9. PHP – operacje na tablicach


Szybki quiz – przypomnienie poprzedniej lekcji

1. Co zostanie wyświetlone?

$tekst = "ąć";
echo strlen($tekst) . " / " . mb_strlen($tekst);

2. Dlaczego ten kod nic nie wyświetla, chociaż tekst zawiera „PHP”? Popraw warunek.

if (strpos("PHP jest super", "PHP")) {
    echo "Znaleziono!";
}

3. Co zwróci wyczyscTytul(" Ala ma kota ")?

function wyczyscTytul(string $tytul): string
{
    $tytul = trim($tytul);
    $tytul = preg_replace('/\s+/', ' ', $tytul);

    return $tytul;
}

4. Funkcja walidujLogin() używa wzorca /^[a-zA-Z0-9_-]{3,20}$/. Dla których loginów zwróci true?

Ka, Kama_2026, Kama 2026, Kama-2026, Kama!!!

5. Usuwamy z wzorca kotwice: /[a-zA-Z0-9_-]{3,20}/. Co teraz zwróci walidujLogin("!!!Kama_2026!!!") i dlaczego?

6. Co zwróci skrocOpis("Powtórzyć funkcje", 9)?

function skrocOpis(string $opis, int $limit = 50): string
{
    if (mb_strlen($opis) <= $limit) {
        return $opis;
    }

    return mb_substr($opis, 0, $limit) . "...";
}

7. $tytulZadania = "Nauka php"; Który warunek jest spełniony?

A. strpos($tytulZadania, 'PHP') !== false
B. stripos($tytulZadania, 'PHP') !== false

8. Dlaczego w walidujLogin() piszemy preg_match(...) === 1, a nie samo preg_match(...)?

Odpowiedzi
  1. 4 / 2 – strlen() liczy bajty (polska litera w UTF-8 to 2 bajty), mb_strlen() liczy znaki.
  2. „PHP” jest na pozycji 0, a 0 w warunku if jest traktowane jak false. Poprawnie: if (strpos("PHP jest super", "PHP") !== false).
  3. "Ala ma kota" – trim() usuwa spacje z brzegów, preg_replace('/\s+/', ' ', ...) zamienia ciągi białych znaków na jedną spację.
  4. Kama_2026 i Kama-2026. Ka – za krótki, Kama 2026 – spacja, Kama!!! – niedozwolone znaki.
  5. true – bez ^ i $ wystarczy, że gdzieś w środku tekstu jest pasujący fragment (Kama_2026). Kotwice wymuszają, by wzorzec pasował do całego tekstu.
  6. "Powtórzyć..." – tekst ma 17 znaków, więc zostaje pierwsze 9 znaków i ....
  7. B – stripos() nie rozróżnia wielkości liter, strpos() tak.
  8. preg_match() zwraca liczbę (1 – dopasowano, 0 – nie) albo false przy błędzie, a funkcja ma zwracać bool. Porównanie === 1 daje dokładnie true lub false.

TaskQuest

Dodatek

Pełną listę funkcji znajdziecie w dokumentacji PHP,  poniżej najczęściej wykorzystywane.

Tablica (ang. array) to uporządkowany zbiór danych przechowywany w jednej zmiennej. Każdy element tablicy ma swój indeks (klucz) i wartość.

Zastosowania:
– Przechowywanie list danych (produkty, użytkownicy, etc.)
– Gromadzenie wyników z bazy danych
– Konfiguracja aplikacji
– Przesyłanie danych między funkcjami

Typy indeksów:
– Numeryczne (0, 1, 2, …) – tablice indeksowane
– Tekstowe (ciągi znaków) – tablice asocjacyjne

Sposób 1:

Inicjowanie tablicy za pomocą nawiasów kwadratowych [].

// Pusta tablica
$tablica = [];

// Tablica z wartościami
$owoce = ['jabłko', 'banan', 'gruszka'];

// Tablica asocjacyjna
$osoba = [
'imie' => 'Jan',
'nazwisko' => 'Kowalski',
'wiek' => 30
];

// Dodawanie elementów
$owoce[] = 'pomarańcza'; // doda na końcu
$owoce[10] = 'kiwi'; // doda pod indeksem 10

W powyższym przykładzie wskazujemy konkretny nr indeksu w jaki ma zostać wpisana wartość np. $owoce[10] = 'kiwi’;. Odczytujemy ten zapis jako: w tablicy o nazwie „owoce” wstaw do indexu „10” wartość „kiwi”.

Podawanie indeksów nie jest konieczne:

$tablica[0] = "Wpis numer 0"; 
$tablica[] = "Wpis numer 1";
$tablica[] = "Wpis numer 2";
echo '<pre>';
print_r($tablica);
echo '</pre>';

/*
Wyświetli:
Array (
[0] => Wpis numer 0
[1] => Wpis numer 1
[2] => Wpis numer 2
)
*/

Nie wskazanie podczas inicjowania tablicy konkretnych indexów danych uruchamia proces autonumerowania od ostatniej najwyższej wartości.

Sposób 2:

Inicjowanie tablicy za pomocą konstrukcji array(). Zalecenie: Używaj składni [] – krótsza i nowocześniejsza

// Pusta tablica
$tablica = array();

// Tablica z wartościami
$owoce = array('jabłko', 'banan', 'gruszka');

// Tablica asocjacyjna
$osoba = array(
'imie' => 'Jan',
'nazwisko' => 'Kowalski',
'wiek' => 30
);

Możemy wskazywać konkretne indeksy, w których chcemy wstawić wartości. Wykonujemy to za pomocą wskazania „index => wartość”.

Trochę więcej o array() niżej.

Elementem tablicy może być każdy typ zmiennej (z innymi tablicami i obiektami włącznie).

Tablica dwuwymiarowa, zawiera dwa indeksy, np. $tablica[0][2] = 1; O nich troszkę niżej.

 Sposób 3:

Przypisywanie indeksowe

$tablica[0] = "Element 0";
$tablica[1] = "Element 1";
$tablica[2] = "Element 2";

// Bez podawania indeksu - auto-numeracja
$tablica[] = "Element 3";  // indeks 3
$tablica[] = "Element 4";  // indeks 4

Indexy zewnetrzne

Typy tablic

Tablica indeksowana (numeryczna)

Indeksy to liczby całkowite (domyślnie od 0).

$kolory = ['czerwony', 'zielony', 'niebieski'];

echo $kolory[0];  // czerwony
echo $kolory[1];  // zielony
echo $kolory[2];  // niebieski

// Pętla for
for ($i = 0; $i < count($kolory); $i++) {
    echo $kolory[$i] . "\n";
}

Tablica asocjacyjna

W PHP występuje też inny rodzaj tablic, tak zwane tablice asocjacyjne. Są to tablice, w których zamiast indeksów liczbowych używa się identyfikatorów znakowych (kluczy), a po polsku – nadajemy indeksom nazwy słowne, tak jak w nagłówkach zwykłej tabeli:

$osoba = [
'imie' => 'Jan',
'nazwisko' => 'Kowalski',
'wiek' => 30,
'miasto' => 'Warszawa'
];

echo $osoba['imie']; // Jan
echo $osoba['nazwisko']; // Kowalski

// Pętla foreach
foreach ($osoba as $klucz => $wartosc) {
echo "$klucz: $wartosc\n";
}

Tablica wielowymiarowa

Tablica zawierająca inne tablice jako elementy.

// Dwuwymiarowa
$uczniowie = [
    ['Jan', 'Kowalski', 15],
    ['Anna', 'Nowak', 16],
    ['Piotr', 'Wiśniewski', 15]
];

echo $uczniowie[0][0];  // Jan
echo $uczniowie[1][1];  // Nowak

// Wielowymiarowa asocjacyjna
$produkty = [
    'elektronika' => [
        'laptop' => 3000,
        'telefon' => 1500,
        'tablet' => 800
    ],
    'odzież' => [
        'koszula' => 100,
        'spodnie' => 150
    ]
];

echo $produkty['elektronika']['laptop'];  // 3000

Podglądanie zawartości tabel

Aby w całości wyświetlić tablicę (podejrzeć jej zawartość) – wykorzystujemy funkcje:
print_r ($zmienna_tablicowa);
lub
var_dump($zmienna_tablicowa);

 print_r()

wyświetla tablice w sposób jasny i przejrzysty dla programisty, pokazując nazwy indeksów:

<pre>
<?php

$a=array(
'a' => 'jabłko',
'b' => 'banan',
'c' => array ('x', 'y', 'z')
);

print_r($a);
?> </pre> /*zostanie wyświetlone: Array ( [a] => jabłko [b] => banan [c] => Array ( [0] => x [1] => y [2] => z ) ) */

Operacje na tablicach

Sprawdzanie typu – is_array()

$zmienna = ['a', 'b', 'c'];

if (is_array($zmienna)) {
    echo "To jest tablica";
} else {
    echo "To nie jest tablica";
}

// Krótka forma
echo is_array($zmienna) ? 'tablica' : 'nie tablica';

Liczba elementów – count() / sizeof()

$owoce = ['jabłko', 'banan', 'gruszka', 'pomarańcza'];

echo count($owoce);   // 4
echo sizeof($owoce);  // 4 (alias count)

// Tablica wielowymiarowa
$multi = [
    [1, 2, 3],
    [4, 5, 6]
];

echo count($multi);              // 2 (tylko pierwsze poziom)
echo count($multi, COUNT_RECURSIVE);  // 8 (wszystkie elementy)

Dodawanie elementów

$owoce = ['jabłko', 'banan'];

// Na końcu
$owoce[] = 'gruszka';
array_push($owoce, 'pomarańcza', 'kiwi');

// Na początku
array_unshift($owoce, 'truskawka');

print_r($owoce);
// ['truskawka', 'jabłko', 'banan', 'gruszka', 'pomarańcza', 'kiwi']

$array[] = $value jest szybsze niż array_push().

Usuwanie elementów

$owoce = ['jabłko', 'banan', 'gruszka', 'pomarańcza'];

// Z końca
$ostatni = array_pop($owoce);
echo $ostatni;  // pomarańcza

// Z początku
$pierwszy = array_shift($owoce);
echo $pierwszy;  // jabłko

// Konkretny indeks
unset($owoce[1]);  // usuń 'banan'

// Wszystkie elementy
unset($owoce);
$owoce = [];  // lub tak

Łączenie tablic

$tablica1 = ['a', 'b', 'c'];
$tablica2 = ['d', 'e', 'f'];

// array_merge() - łączy tablice
$polaczona = array_merge($tablica1, $tablica2);
print_r($polaczona);
// ['a', 'b', 'c', 'd', 'e', 'f']

// Operator + (zachowuje klucze)
$tab1 = [0 => 'a', 1 => 'b'];
$tab2 = [0 => 'c', 2 => 'd'];
$wynik = $tab1 + $tab2;
print_r($wynik);
// [0 => 'a', 1 => 'b', 2 => 'd']  // klucz 0 z $tab2 został pominięty

// Spread operator (PHP 7.4+)
$polaczona = [...$tablica1, ...$tablica2];

Wyszukiwanie w tablicy


PHP
$owoce = ['jabłko', 'banan', 'gruszka'];

// in_array() - sprawdza czy wartość istnieje
if (in_array('banan', $owoce)) {
    echo "Jest banan!";
}

// array_search() - zwraca klucz (indeks)
$indeks = array_search('gruszka', $owoce);
echo $indeks;  // 2

// array_key_exists() - sprawdza czy klucz istnieje
$osoba = ['imie' => 'Jan', 'wiek' => 30];
if (array_key_exists('imie', $osoba)) {
    echo "Klucz 'imie' istnieje";
}

// isset() - szybsza alternatywa
if (isset($osoba['imie'])) {
    echo "Klucz 'imie' istnieje";

Wyciąganie kluczy i wartości

$osoba = [
    'imie' => 'Jan',
    'nazwisko' => 'Kowalski',
    'wiek' => 30
];

// Wszystkie klucze
$klucze = array_keys($osoba);
print_r($klucze);
// ['imie', 'nazwisko', 'wiek']

// Wszystkie wartości
$wartosci = array_values($osoba);
print_r($wartosci);
// ['Jan', 'Kowalski', 30]

// Odwrócenie kluczy z wartościami
$odwrocona = array_flip($osoba);
print_r($odwrocona);
// ['Jan' => 'imie', 'Kowalski' => 'nazwisko', 30 => 'wiek']

Przeglądanie tablic

Pętla for (dla tablic indeksowanych)

$liczby = [10, 20, 30, 40, 50];

for ($i = 0; $i < count($liczby); $i++) {
    echo $liczby[$i] . "\n";
}

// Lepiej - zachowaj count w zmiennej
$dlugosc = count($liczby);
for ($i = 0; $i < $dlugosc; $i++) {
    echo $liczby[$i] . "\n";
}

Pętla foreach (uniwersalna, zalecana)

// Tylko wartości
$owoce = ['jabłko', 'banan', 'gruszka'];

foreach ($owoce as $owoc) {
    echo $owoc . "\n";
}

// Klucze i wartości
$osoba = ['imie' => 'Jan', 'wiek' => 30];

foreach ($osoba as $klucz => $wartosc) {
    echo "$klucz: $wartosc\n";
}

// 🆕 Modyfikacja przez referencję
$liczby = [1, 2, 3, 4, 5];

foreach ($liczby as &$liczba) {
    $liczba *= 2;
}
unset($liczba);  // WAŻNE: zawsze unset po pętli z referencją!

print_r($liczby);
// [2, 4, 6, 8, 10]

Funkcja list() – destrukturyzacja

// Podstawowe użycie
$info = ['kawa', 'brązowa', 'kofeina'];
list($napoj, $kolor, $skladnik) = $info;

echo "$napoj jest $kolor a $skladnik czyni ją wyjątkową";
// kawa jest brązowa a kofeina czyni ją wyjątkową

// Pomijanie elementów
list($napoj, , $skladnik) = $info;
echo "$napoj zawiera $skladnik";
// kawa zawiera kofeina

// Krótka składnia (PHP 7.1+)
[$napoj, $kolor, $skladnik] = $info;

// Z kluczami asocjacyjnymi (PHP 7.1+)
$osoba = ['imie' => 'Jan', 'wiek' => 30, 'miasto' => 'Warszawa'];
['imie' => $imie, 'wiek' => $wiek] = $osoba;
echo "$imie ma $wiek lat";

reset() – ustawianie wskaźnika

$tablica = ['a', 'b', 'c'];

// Przesuń wskaźnik
next($tablica);
next($tablica);
echo current($tablica);  // c

// Zresetuj na początek
reset($tablica);
echo current($tablica);  // a

W nowoczesnym PHP rzadko używane – preferuj foreach.

Sortowanie tablic

sort() / rsort() – sortowanie wartości (indeksowane)

$liczby = [5, 2, 8, 1, 9];

// Rosnąco
sort($liczby);
print_r($liczby);  // [1, 2, 5, 8, 9]

// Malejąco
rsort($liczby);
print_r($liczby);  // [9, 8, 5, 2, 1]

// ⚠️ Uwaga: indeksy są resetowane!
$owoce = [2 => 'banan', 0 => 'jabłko', 1 => 'gruszka'];
sort($owoce);
print_r($owoce);
// [0 => 'banan', 1 => 'gruszka', 2 => 'jabłko']

asort() / arsort() – sortowanie wartości (asocjacyjne)

Zachowuje przypisanie kluczy do wartości.

$owoce = [
    'd' => 'mango',
    'a' => 'papaja',
    'b' => 'banan',
    'c' => 'aronia'
];

// Rosnąco
asort($owoce);
print_r($owoce);
// ['c' => 'aronia', 'b' => 'banan', 'd' => 'mango', 'a' => 'papaja']

// Malejąco
arsort($owoce);
print_r($owoce);
// ['a' => 'papaja', 'd' => 'mango', 'b' => 'banan', 'c' => 'aronia']

ksort() / krsort() – sortowanie kluczy

$owoce = [
    'd' => 'mango',
    'a' => 'papaja',
    'b' => 'banan',
    'c' => 'aronia'
];

// Rosnąco po kluczach
ksort($owoce);
print_r($owoce);
// ['a' => 'papaja', 'b' => 'banan', 'c' => 'aronia', 'd' => 'mango']

// Malejąco po kluczach
krsort($owoce);
print_r($owoce);
// ['d' => 'mango', 'c' => 'aronia', 'b' => 'banan', 'a' => 'papaja']

usort() / uasort() / uksort() – sortowanie własną funkcją

$osoby = [
    ['imie' => 'Jan', 'wiek' => 30],
    ['imie' => 'Anna', 'wiek' => 25],
    ['imie' => 'Piotr', 'wiek' => 35]
];

// usort() - sortowanie wartości
usort($osoby, function($a, $b) {
    return $a['wiek'] <=> $b['wiek'];  // sortuj po wieku
});

print_r($osoby);
// [['imie' => 'Anna', 'wiek' => 25], ['imie' => 'Jan', 'wiek' => 30], ...]

// Arrow function (PHP 7.4+)
usort($osoby, fn($a, $b) => $a['wiek'] <=> $b['wiek']);

// uasort() - zachowuje klucze
$oceny = ['Jan' => 4, 'Anna' => 5, 'Piotr' => 3];
uasort($oceny, fn($a, $b) => $b <=> $a);  // malejąco
print_r($oceny);
// ['Anna' => 5, 'Jan' => 4, 'Piotr' => 3]

Tabela funkcji sortujących

FunkcjaSortuje poZachowuje kluczeKierunek
sort()Wartościach✗Rosnąco
rsort()Wartościach✗Malejąco
asort()Wartościach✓Rosnąco
arsort()Wartościach✓Malejąco
ksort()Kluczach✓Rosnąco
krsort()Kluczach✓Malejąco
usort()Wartościach (custom)✗Custom
uasort()Wartościach (custom)✓Custom
uksort()Kluczach (custom)✓Custom

Funkcje tablicowe

explode() / implode() – string ↔ array

// explode() - dzieli string na tablicę
$tekst = "alfa;beta;gamma;delta";
$tablica = explode(";", $tekst);
print_r($tablica);
// ['alfa', 'beta', 'gamma', 'delta']

// Z limitem
$tablica = explode(";", $tekst, 2);
print_r($tablica);
// ['alfa', 'beta;gamma;delta']

// implode() / join() - łączy tablicę w string
$tekst = implode(";", $tablica);
echo $tekst;  // alfa;beta;gamma;delta

str_split() – dzieli string na znaki

$str = "Hello";

// Na pojedyncze znaki
$znaki = str_split($str);
print_r($znaki);
// ['H', 'e', 'l', 'l', 'o']

// Na fragmenty długości 2
$fragmenty = str_split($str, 2);
print_r($fragmenty);
// ['He', 'll', 'o']

array_slice() – wycina fragment tablicy

$owoce = ['jabłko', 'banan', 'gruszka', 'pomarańcza', 'kiwi'];

// Od indeksu 1, 3 elementy
$fragment = array_slice($owoce, 1, 3);
print_r($fragment);
// ['banan', 'gruszka', 'pomarańcza']

// Od końca
$ostatnie = array_slice($owoce, -2);
print_r($ostatnie);
// ['pomarańcza', 'kiwi']

// Zachowaj klucze
$fragment = array_slice($owoce, 1, 3, true);

array_splice() – usuwa/zastępuje fragment

$owoce = ['jabłko', 'banan', 'gruszka', 'pomarańcza'];

// Usuń 2 elementy od indeksu 1
$usuniete = array_splice($owoce, 1, 2);
print_r($owoce);     // ['jabłko', 'pomarańcza']
print_r($usuniete);  // ['banan', 'gruszka']

// Zastąp
$owoce = ['jabłko', 'banan', 'gruszka'];
array_splice($owoce, 1, 1, ['kiwi', 'mango']);
print_r($owoce);
// ['jabłko', 'kiwi', 'mango', 'gruszka']

// Wstaw bez usuwania
array_splice($owoce, 2, 0, ['truskawka']);

array_unique() – usuwa duplikaty

$liczby = [1, 2, 2, 3, 4, 4, 5];
$unikalne = array_unique($liczby);
print_r($unikalne);
// [0 => 1, 1 => 2, 3 => 3, 4 => 4, 6 => 5]

// Zresetuj indeksy
$unikalne = array_values(array_unique($liczby));
print_r($unikalne);
// [1, 2, 3, 4, 5]

array_reverse() – odwraca tablicę

$owoce = ['jabłko', 'banan', 'gruszka'];
$odwrocone = array_reverse($owoce);
print_r($odwrocone);
// ['gruszka', 'banan', 'jabłko']

// Zachowaj klucze
$odwrocone = array_reverse($owoce, true);

array_sum() / array_product()

$liczby = [1, 2, 3, 4, 5];

// Suma
echo array_sum($liczby);  // 15

// Iloczyn
echo array_product($liczby);  // 120

array_rand() – losowy element

$owoce = ['jabłko', 'banan', 'gruszka', 'pomarańcza'];

// Jeden losowy klucz
$klucz = array_rand($owoce);
echo $owoce[$klucz];

// Trzy losowe klucze
$klucze = array_rand($owoce, 3);
foreach ($klucze as $k) {
    echo $owoce[$k] . "\n";
}

array_chunk() – dzieli tablicę na mniejsze

$liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

$chunki = array_chunk($liczby, 3);
print_r($chunki);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// Zachowaj klucze
$chunki = array_chunk($liczby, 3, true);

array_map() – transformacja elementów

$liczby = [1, 2, 3, 4, 5];

// Podwój każdą liczbę
$podwojone = array_map(function($x) {
    return $x * 2;
}, $liczby);
print_r($podwojone);
// [2, 4, 6, 8, 10]

// Arrow function (PHP 7.4+)
$podwojone = array_map(fn($x) => $x * 2, $liczby);

// Wiele tablic
$imiona = ['Jan', 'Anna'];
$nazwiska = ['Kowalski', 'Nowak'];

$pelne = array_map(function($imie, $nazwisko) {
    return "$imie $nazwisko";
}, $imiona, $nazwiska);
print_r($pelne);
// ['Jan Kowalski', 'Anna Nowak']

// Spread + array_map (PHP 7.4+)
$kwadraty = array_map(fn($x) => $x ** 2, range(1, 10));

array_filter() – filtrowanie elementów

$liczby = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Tylko parzyste
$parzyste = array_filter($liczby, function($x) {
    return $x % 2 == 0;
});
print_r($parzyste);
// [1 => 2, 3 => 4, 5 => 6, 7 => 8, 9 => 10]

// Arrow function
$parzyste = array_filter($liczby, fn($x) => $x % 2 == 0);

// Filtruj z kluczami
$osoby = ['Jan' => 25, 'Anna' => 17, 'Piotr' => 30];
$pelnoletni = array_filter($osoby, fn($wiek) => $wiek >= 18);
print_r($pelnoletni);
// ['Jan' => 25, 'Piotr' => 30]

// ARRAY_FILTER_USE_KEY - filtruj po kluczach
$filtered = array_filter($osoby, fn($klucz) => strlen($klucz) > 3, ARRAY_FILTER_USE_KEY);

array_reduce() – redukuje do pojedynczej wartości

$liczby = [1, 2, 3, 4, 5];

// Suma
$suma = array_reduce($liczby, function($carry, $item) {
    return $carry + $item;
}, 0);
echo $suma;  // 15

// Arrow function
$suma = array_reduce($liczby, fn($carry, $item) => $carry + $item, 0);

// Iloczyn
$iloczyn = array_reduce($liczby, fn($c, $i) => $c * $i, 1);
echo $iloczyn;  // 120

// Konkatenacja
$slowa = ['Hello', 'World', 'PHP'];
$zdanie = array_reduce($slowa, fn($c, $i) => $c . ' ' . $i, '');
echo trim($zdanie);  // Hello World PHP

array_walk() – modyfikacja w miejscu

$liczby = [1, 2, 3, 4, 5];

// Podwój każdy element
array_walk($liczby, function(&$wartosc, $klucz) {
    $wartosc *= 2;
});
print_r($liczby);
// [2, 4, 6, 8, 10]

// Z dodatkowym parametrem
array_walk($liczby, function(&$v, $k, $mnoznik) {
    $v *= $mnoznik;
}, 3);

array_column() – wyciąga kolumnę z wielowymiarowej

$osoby = [
    ['id' => 1, 'imie' => 'Jan', 'wiek' => 30],
    ['id' => 2, 'imie' => 'Anna', 'wiek' => 25],
    ['id' => 3, 'imie' => 'Piotr', 'wiek' => 35]
];

// Wyciągnij wszystkie imiona
$imiona = array_column($osoby, 'imie');
print_r($imiona);
// ['Jan', 'Anna', 'Piotr']

// Z kluczem
$iminaPoId = array_column($osoby, 'imie', 'id');
print_r($iminaPoId);
// [1 => 'Jan', 2 => 'Anna', 3 => 'Piotr']

Spread operator (PHP 7.4+)

$tab1 = [1, 2, 3];
$tab2 = [4, 5, 6];

// Łączenie
$polaczona = [...$tab1, ...$tab2];
print_r($polaczona);
// [1, 2, 3, 4, 5, 6]

// W argumentach funkcji
function suma(...$liczby) {
    return array_sum($liczby);
}

echo suma(1, 2, 3, 4, 5);  // 15
echo suma(...[10, 20, 30]);  // 60

// W array_merge
$wynik = [...$tab1, 'x', 'y', ...$tab2];
// [1, 2, 3, 'x', 'y', 4, 5, 6]

Null coalescing dla tablic (PHP 7.0+)

$config = ['db' => 'mysql', 'port' => 3306];

// Stary sposób
$host = isset($config['host']) ? $config['host'] : 'localhost';

// Null coalescing
$host = $config['host'] ?? 'localhost';

// Łańcuchowe (PHP 7.4+)
$wartość = $config['a'] ?? $config['b'] ?? $config['c'] ?? 'domyślna';

 Tablice superglobalne

Tablice superglobalne w PHP to automatycznie dostępne, globalne tablice zawierające dane zebrane od serwera, użytkownika lub z sesji, co oznacza, że można ich używać w dowolnym miejscu skryptu bez potrzeby ich deklarowania. 

$_GET

Przechowuje dane przekazane do skryptu za pomocą metody GET (np. parametry w adresie URL).

// URL: http://example.com/page.php?name=Jan&age=30

echo $_GET['name'];  // Jan
echo $_GET['age'];   // 30

// Bezpieczne pobieranie
$name = $_GET['name'] ?? 'Gość';
$age = filter_input(INPUT_GET, 'age', FILTER_VALIDATE_INT) ?? 0;

$_POST

Zawiera dane wprowadzone przez użytkownika w formularzach przesłanych metodą POST.

// Formularz wysłany metodą POST

$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';

// ⚠️ Walidacja!
$username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

$_SERVER

Dostarcza informacji o serwerze i środowisku, w tym dane o połączeniu, adresie IP czy nagłówkach HTTP.

// Najczęściej używane:
echo $_SERVER['HTTP_HOST'];        // example.com
echo $_SERVER['REQUEST_URI'];      // /page.php?id=1
echo $_SERVER['REQUEST_METHOD'];   // GET, POST, etc.
echo $_SERVER['REMOTE_ADDR'];      // IP klienta
echo $_SERVER['HTTP_USER_AGENT'];  // przeglądarka
echo $_SERVER['SCRIPT_NAME'];      // /path/script.php
echo $_SERVER['DOCUMENT_ROOT'];    // ścieżka root serwera

// Pełna lista
echo '<pre>';
print_r($_SERVER);
echo '</pre>';

$_FILES

Używana do przetwarzania plików przesłanych do skryptu metodą POST.

// Formularz z <input type="file" name="plik">

if (isset($_FILES['plik'])) {
    $file = $_FILES['plik'];
    
    echo $file['name'];      // nazwa pliku
    echo $file['type'];      // typ MIME
    echo $file['size'];      // rozmiar w bajtach
    echo $file['tmp_name'];  // tymczasowa lokalizacja
    echo $file['error'];     // kod błędu (0 = OK)
    
    // Przeniesienie pliku
    if ($file['error'] === 0) {
        move_uploaded_file($file['tmp_name'], 'uploads/' . $file['name']);
    }
}

$_COOKIE

Zawiera ciasteczka (cookies) wysłane przez serwer WWW, które są dostępne w przeglądarce użytkownika.

// Ustawianie cookie
setcookie('username', 'Jan', time() + 3600);  // 1 godzina

// Odczytywanie
$username = $_COOKIE['username'] ?? 'Gość';

$_SESSION

Przechowuje dane użytkownika między różnymi stronami lub żądaniami (dane sesji).

// Rozpocznij sesję
session_start();

// Zapisz dane
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'Jan';

// Odczytaj dane
$userId = $_SESSION['user_id'] ?? null;

// Usuń zmienną
unset($_SESSION['username']);

// Zniszcz całą sesję
session_destroy();

$_ENV

Zawiera zmienne środowiskowe dostępne dla skryptu PHP.

$_REQUEST  – $_GET + $_POST + $_COOKIE

Jest to tablica, która zawiera dane zebrane jednocześnie z $_GET, $_POST i $_COOKIE

// Nie zalecane - lepiej używać konkretnych tablic
$id = $_REQUEST['id'] ?? 0;

// Lepiej:
$id = $_GET['id'] ?? $_POST['id'] ?? 0;

$GLOBALS

(ta jedna nie ma podłogi w nazwie) Tablica zawierająca odniesienia do wszystkich zmiennych o zasięgu globalnym w danym skrypcie.

$x = 10;
$y = 20;

function suma() {
    return $GLOBALS['x'] + $GLOBALS['y'];
}

echo suma();  // 30

// Nie zalecane - lepiej przekazywać jako argumenty

Jeżeli chcemy zobaczyć co dana tablica zawiera, wystarczy że wykonamy poniższy kod

echo "<pre>";
print_r($_SERVER); //tutaj wpisujemy nazwę tablicy
echo "</pre>";

Zadania:

1. Utworzyć dowolną tablicę asocjacyjną, posortować ją rosnąco i malejąco według wartości i wyświetlić elementy kolejno na ekranie.

2. Napisz skrypt obliczający sumę elementów w tablicy.

3. Utworzyć tablicę zawierającą kąty i kolejne wartości funkcji sin(x) w zakresie od 0 do 900 z krokiem 50 wyświetlić jej zawartość na ekranie: w 2 kolumnach (kąt –  wartość).

4. Wypełnij tablicę 1000 losowych liczb z zakresu od 1 do 10000. Wyznacz:

a) ile jest w tablicy liczb parzystych
b) ile jest w tablicy liczb nieparzystych
c) najmniejszą i największą liczbę w tablicy
d) średnią arytmetyczną wszystkich liczb z tablicy

5. (dla klas, które miały HTMLa) Napisz skrypt, który za pomocą formularza przekaże dane osobowe do tablicy (imię, nazwisko, klasa, grupa) i wyświetli je na ekranie.

5. (dla klas, które nie miały HTMLa) Napisz skrypt, który rozdzieli po spacji podany ciąg (imię, nazwisko, klasa, grupa) dodając wydzielone elementy do tablicy i wyświetli ją na ekranie.

6. Napisz skrypt, który stworzy tablicę wielowymiarową zawierająca następujące dane: nazwy kontynentów (min 3) -> państwa mieszczące się na danym kontynencie (min 3) -> miasta w danym państwie (min 3). Posortuj tablicę rosnąco wg klucza: kontynent, państwo, miasto.

7. Utwórz tablicę czteroelementową zawierającą następujące wartości: “Fioletowy”, “Niebieski”, “Różowy”, “Zielony”. Następnie użyj jej do wyświetlenia listy:

  • Fioletowy
  • Niebieskie
  • Różowy
  • Zielony

8. Zsumuj wszystkie elementy z tablicy: $wartosci = [1, 15, 12, 51, 08, -11]. Wykorzystaj pętle for

9. Oblicz temperaturę średnią. Posłuż się tablicą $wartosci przechowującą odczytane temperatury:

$wartosci = [20, 17, 22, 20, 16, 31, 22, 15, 13, -12, 16, 7, 1, 0, -7, -10];

10. Wyświetl wszystkie elementy tablicy: $auta = [„VW”, „Audi”, „BMW”, „Mercedes”]. Użyj pętli foreach.

11. Napisz skrypt odnajdujący największy element tablicy:
$liczby = [10, 15, 9, 61, 78, 2, 44, 21, 30];

12. Napisz skrypt, który wyświetla nazwę kraju i jego stolicę . Posłuż się tablicą $europa:
$europa = array(„Włochy” => „Rzym”, „Belgia” => „Bruksela”,
„Dania” => „Kopenhaga”, „Finlandia” => „Helsinki”,
„Francja => „Paryż”, „Niemcy” => „Berlin”,
„Rosja” => „Moskwa”, „Polska” => „Warszawa”);
// tu umieść kod wyświetlający informacje wg schematu:
// Kraj: Włochy, stolica: Rzym
// …
// Kraj: Polska, stolica: Warszawa

13. Napisać funkcję o nazwie ListaLiczb (o 3 parametrach: $zakres1, $zakres2, $ilosc) wczytujący do utworzonej wewnątrz funkcji tablicy $tab losowe liczby całkowite z zakresu od $zakres1 do
$zakres2 w ilości $ilosc.
Należy przy tym najpierw sprawdzić, czy $zakres1 jest mniejsze lub równe $zakres2 – jeśli nie, to należy zamienić między sobą obie liczby.
Dodatkowo, jeśli $ilosc nie jest większa od zera, to wyświetlić komunikat o błędzie („Ilość elementów w tablicy musi być większa od 0.”) i przerwać działanie funkcji.
Ostatecznie funkcja wyświetla liczby z tablicy w postaci listy nienumerowanej.

Ściągawka funkcji tablicowych

KategoriaFunkcje
Tworzenie[], array(), range(), array_fill()
Dodawanie[], array_push(), array_unshift()
Usuwaniearray_pop(), array_shift(), unset()
Wyszukiwaniein_array(), array_search(), array_key_exists()
Sortowaniesort(), rsort(), asort(), arsort(), ksort(), krsort()
Transformacjaarray_map(), array_filter(), array_reduce()
Łączeniearray_merge(), +, … (spread)
Dzieleniearray_slice(), array_chunk()
Informacjecount(), sizeof(), empty(), isset()
String ↔ Arrayexplode(), implode(), str_split()

Dobre praktyki

1. Preferuj [] nad array()

// ŹLE - stara składnia
$tab = array(1, 2, 3);

// DOBRZE - nowoczesna składnia
$tab = [1, 2, 3];

2. Używaj array_map/filter zamiast pętli

// ŹLE - niepotrzebna pętla
$podwojone = [];
foreach ($liczby as $liczba) {
    $podwojone[] = $liczba * 2;
}

// DOBRZE - array_map
$podwojone = array_map(fn($x) => $x * 2, $liczby);

3. Unset referencji po foreach

foreach ($tablica as &$wartosc) {
    $wartosc *= 2;
}
unset($wartosc);  // ZAWSZE!

4. Waliduj dane z superglobalnych

// ŹLE
$id = $_GET['id'];

// DOBRZE
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT) ?? 0;

5. Używaj spread operator dla łączenia

// ŹLE - wolniejsze
$polaczona = array_merge($tab1, $tab2, $tab3);

// DOBRZE - szybsze (PHP 7.4+)
$polaczona = [...$tab1, ...$tab2, ...$tab3];

TaskQuest 9 — powstaje prawdziwa lista zadań

KARTA PRACY

Do tej pory TaskQuest znał jedno zadanie, zapisane w kilku osobnych zmiennych:

$tytulZadania
$opisZadania
$terminZadania
$zadanieWykonane

Prawdziwa aplikacja do zarządzania zadaniami musi jednak pokazać ich dziesiątki. Nie możemy przecież tworzyć $tytulZadania1, $tytulZadania2, $tytulZadania3…

Cel lekcji

Nauczyć TaskQuest:

  • przechowywać wiele zadań w jednej zmiennej (tablica tablic asocjacyjnych),
  • wyświetlać całą listę zadań jedną pętlą foreach,
  • liczyć zadania i sumować XP,
  • wybierać zadania wykonane i aktywne (array_filter()),
  • sortować zadania według terminu (usort()),
  • sprawdzać, czy aktywne zadania wystarczą na awans na kolejny poziom.

Wszystkie funkcje z Lekcji 7 i 8 (komunikatTerminu(), aktualizujStreak(), wyczyscTytul(), skrocOpis(), walidujLogin()) zostają i zaczną pracować na elementach listy.

Uwaga: to pierwsza lekcja, w której świadomie używamy tablic. Dane nadal są zapisane „na sztywno” w kodzie (nie ma jeszcze formularzy, plików ani bazy danych), więc lista zadań jest na tym etapie symulowana. Przygotowujemy logikę, którą w kolejnych lekcjach podłączymy do prawdziwych źródeł danych.

Porządki na start — trzy poprawki w kodzie z Lekcji 8

Zanim dołożymy nowy temat, poprawiamy trzy drobiazgi z Lekcji 8. Zrób to teraz, w swoim pliku TaskQuest.

Poprawka 1: $login zamiast $loginUzytkownika

CO JEST PROBLEMEM?
W materiale z Lekcji 8 login raz nazywał się $login, a raz $loginUzytkownika.
        ↓
DLACZEGO TO PROBLEM?
To ta sama informacja pod dwiema nazwami. Kod z jedną z nich może przestać działać
(np. "Undefined variable"), a w kolejnych lekcjach (tablice, baza danych) nazwy
zaczną się mnożyć.
        ↓
CO ZMIENIAMY?
Zostaje wyłącznie $login — do końca kursu. Ta sama nazwa będzie kluczem 'login'
w tablicach i nazwą kolumny w bazie danych.
        ↓
DLACZEGO TAK?
Jedna informacja = jedna nazwa. $imieUzytkownika (imię) i $login (login)
to natomiast dwie RÓŻNE informacje — obie zostają.
        ↓
JAK WYGLĄDA NOWY KOD?
$login = "Kama_2026";

if (walidujLogin($login)) {
    echo "<p>Login poprawny: $login</p>";
}

Sprawdź w całym pliku, czy nigdzie nie została nazwa $loginUzytkownika (użyj wyszukiwania w edytorze).

Poprawka 2: ++$poziom zamieniamy na ($poziom + 1)

CO JEST PROBLEMEM?
W nagłówku z paskiem postępu numer następnego poziomu liczyliśmy przez ++$poziom.
        ↓
DLACZEGO TO PROBLEM?
++$poziom TRWALE zwiększa wartość zmiennej. Od tej linii $poziom przestaje oznaczać
aktualny poziom użytkownika. Działało "przypadkiem", bo nic dalej z $poziom nie korzystało.
Od tej lekcji korzystamy z $poziom niżej (komunikat o awansie) — błąd byłby widoczny.
        ↓
CO ZMIENIAMY?
Numer następnego poziomu liczymy "obok": ($poziom + 1), bez zmiany $poziom.
        ↓
DLACZEGO TAK?
$poziom ma przez cały skrypt znaczenie "aktualny poziom".
        ↓
JAK WYGLĄDA NOWY KOD?
<?= "Masz $xpWAktualnymPoziomie XP z " . XP_NA_POZIOM . " XP potrzebnych do poziomu " . ($poziom + 1) . " (" . round($procentPostepu, 2) . "%)"; ?>

Poprawka 3: literówka w komunikatTerminu()

CO JEST PROBLEMEM?
Komunikat o przekroczonym terminie kończył się dziwnie: "...dni temu.!"
        ↓
DLACZEGO TO PROBLEM?
To zwykła literówka (dwa znaki interpunkcyjne), ale będzie widoczna przy każdym
przeterminowanym zadaniu na liście.
        ↓
CO ZMIENIAMY?
Tylko tekst w jednej gałęzi funkcji. Logika funkcji się nie zmienia.
        ↓
JAK WYGLĄDA NOWY KOD?
return "Termin minął {$roznica->days} dni temu!";

1. Wracamy do naszego TaskQuest

Mamy działającą aplikację, która potrafi:

  • policzyć poziom, tytuł i postęp XP,
  • sprawdzić login,
  • oczyścić tytuł i skrócić opis zadania,
  • powiedzieć, ile dni zostało do terminu,
  • zaktualizować streak.

Zostaje

$imieUzytkownika, $login
$xp, $poziom, $tytul, $xpWAktualnymPoziomie, $procentPostepu, $brakujeXp,
$liczbaAktywnychSegmentow, $progNastepnegoPoziomu
$streak, $dataOstatniejAktywnosci

oraz wszystkie dotychczasowe funkcje.

Zmieni się

Pojedyncze zmienne zadania ($tytulZadania, $opisZadania, $terminZadania) przechodzą do jednej struktury: tablicy $zadania.

Uwaga na nazwy: $tytul (bez „Zadania”) to nadal tytuł gracza („Nowicjusz”). Tytuł zadania będzie od teraz kluczem 'tytul' wewnątrz tablicy zadania. To dwie różne rzeczy — nie pomyl ich.

2. Problem w aplikacji

Użytkownik TaskQuest chce zobaczyć:

[ ] Powtórzyć funkcje — 15 XP
[ ] Ćwiczenia z DateTime — 10 XP
[X] Nauczyć się regexów PHP — 20 XP
...

Zadań będzie raz pięć, a raz pięćdziesiąt. Kod wyświetlający listę nie może się zmieniać za każdym razem, gdy zadanie dochodzi lub znika.

Potrzebujemy:

  1. sposobu przechowania wielu wartości w jednej zmiennej,
  2. sposobu, żeby „przejść” przez wszystkie z nich.

3. Nowy mechanizm PHP: tablice

Tablica to uporządkowany zbiór wartości w jednej zmiennej. Każda wartość ma swój klucz (indeks).

Tablica indeksowana — klucze to numery od 0

$xpZaDni = [10, 25, 5];

echo $xpZaDni[0];       // 10  (numeracja zaczyna się od 0!)
echo count($xpZaDni);   // 3   (ile elementów)

$xpZaDni[] = 40;        // dopisuje element na końcu

Tablica asocjacyjna — klucze to nazwy

$gracz = [
    'login' => 'Kama_2026',
    'xp' => 99
];

echo $gracz['login'];   // Kama_2026

To dokładnie to, czego potrzebujemy do opisania jednego zadania: klucz 'tytul', klucz 'termin', klucz 'xp'…

Tablica tablic — lista rekordów

$gracze = [
    ['login' => 'Kama_2026', 'xp' => 99],
    ['login' => 'Adam_12',   'xp' => 40]
];

echo $gracze[1]['login'];   // Adam_12  (drugi gracz, klucz 'login')

Podglądanie zawartości: print_r()

Kiedy nie wiesz, co jest w tablicy — wypisz ją. Tag <pre> zachowuje wcięcia:

echo '<pre>';
print_r($gracze);
echo '</pre>';

Przechodzenie po tablicy: foreach

foreach ($gracze as $gracz) {
    echo $gracz['login'] . " — " . $gracz['xp'] . " XP<br>";
}

W każdym obrocie pętli $gracz to kolejny element tablicy $gracze. Nie zmienia się nic w kodzie, gdy na liście jest 2 albo 200 graczy.

4. SAMODZIELNIE — jedno zadanie jako tablica

Utwórz nowy plik brudnopis.php (tam poćwiczymy, zanim ruszymy TaskQuest).

Dane zadania z Lekcji 8 zapisz w jednej tablicy asocjacyjnej $zadanie z kluczami:

id, tytul, opis, termin, wykonane, xp

Wartości:

id       → 1
tytul    → "   Nauczyć   się regexów   PHP   "   (z zapasem spacji — zaraz się przyda)
opis     → "Powtórzyć wyrażenia regularne przed następną lekcją."
termin   → "2026-09-20"
wykonane → true
xp       → 20

Wyświetl tytuł zadania.

HINT

Wartość przypisujesz do klucza zapisem:

'klucz' => wartość
POMOC
$zadanie = [
    'id' => 1,
    'tytul' => '...',
    // pozostałe klucze
];

echo $zadanie['tytul'];
ROZWIĄZANIE
<?php
$zadanie = [
    'id' => 1,
    'tytul' => '   Nauczyć   się regexów   PHP   ',
    'opis' => 'Powtórzyć wyrażenia regularne przed następną lekcją.',
    'termin' => '2026-09-20',
    'wykonane' => true,
    'xp' => 20
];

echo $zadanie['tytul'];

Zwróć uwagę: 'xp' to nagroda za wykonanie zadania. To nowa informacja — do tej pory zadanie nie miało wartości XP.

5. SAMODZIELNIE — lista zadań

W brudnopis.php zbuduj tablicę $zadania, w której każdy element to tablica asocjacyjna zadania (tak jak $gracze w przykładzie). Dane:

idtytulopisterminwykonanexp
1 Nauczyć się regexów PHP Powtórzyć wyrażenia regularne przed następną lekcją.2026-09-20true20
2Powtórzyć funkcjePrzejrzeć wszystkie funkcje TaskQuest i sprawdzić ich parametry oraz typy zwracanych wartości.2026-09-16false15
3Ćwiczenia z DateTimeNapisać trzy własne przykłady z diff() i format().2026-09-18false10
4Nauczyć się tablic PHPZbudować listę zadań TaskQuest jako tablicę tablic asocjacyjnych.2026-09-25false30
5Przygotować projekt do Lekcji 10Zastanowić się, jak podzielić TaskQuest na pliki i katalogi.2026-09-22true25

Wypisz $zadania przez print_r() w <pre> i sprawdź, czy struktura wygląda tak, jak zamierzałeś/aś.

HINT

To lista w liście: nawias [ na zewnątrz, a w środku pięć nawiasów [ ... ] oddzielonych przecinkami.

POMOC
$zadania = [
    [
        'id' => 1,
        // ...
    ],
    [
        'id' => 2,
        // ...
    ],
    // ...
];
ROZWIĄZANIE

Pełna lista dokładnie w tej postaci jest w kodzie referencyjnym (sekcja 13).

6. Włączamy listę do TaskQuest

Teraz wracamy do pliku TaskQuest. Robimy krok po kroku, żeby nic nie zgubić.

Krok 1 — usuwamy pojedyncze zmienne zadania

Usuń z kodu z Lekcji 8:

$tytulZadania = "   Nauczyć   się regexów   PHP   ";
$tytulZadania = wyczyscTytul($tytulZadania);
$opisZadania = "Powtórzyć wyrażenia regularne przed następną lekcją.";
$terminZadania = "2026-09-20";

Dlaczego? Dane tego zadania będą teraz jednym z elementów tablicy $zadania. Gdybyśmy zostawili obie wersje, mielibyśmy tę samą informację w dwóch miejscach (a jeśli zmienisz jedną, druga się „rozjedzie”).

Funkcje wyczyscTytul() i skrocOpis() zostają bez zmian — dopiero teraz będą naprawdę potrzebne, bo tytuły i opisy przyjdą z listy.

Krok 2 — wklejamy $zadania

Pod funkcją skrocOpis() wklej tablicę $zadania z brudnopisu.

Krok 3 — wyświetlamy listę

W panelu HTML, w miejscu dawnych linii z Zadanie:, opisem, Termin:, Komunikat:, Stan: i informacją o PHP, wyświetlimy teraz całą listę zadań.

SAMODZIELNIE

Wyświetl w pętli foreach każde zadanie z listy. Dla każdego zadania pokaż:

[ ] albo [X]  + tytuł (oczyszczony!) + XP
skrócony opis (max 50 znaków)
Termin: ...  + komunikat o terminie (tylko dla zadań, które NIE są wykonane)
"To zadanie dotyczy PHP." (jeśli tytuł zawiera PHP)

Wykorzystaj funkcje, które już masz: wyczyscTytul(), skrocOpis(), komunikatTerminu() i stripos().

HINT
  • Znacznik zadania: operator ? : (znasz go ze zmiennej $status),
  • Wartości z elementu odczytujesz jak z każdej tablicy asocjacyjnej: $zadanie['termin'],
  • Zadanie jest „nie wykonane”, gdy !$zadanie['wykonane'].
POMOC
foreach ($zadania as $zadanie) {
    $tytulCzysty = wyczyscTytul($zadanie['tytul']);
    $znacznik = $zadanie['wykonane'] ? "[X]" : "[ ]";

    echo '<div class="zadanie">';
    echo "<p><strong>$znacznik $tytulCzysty</strong> — {$zadanie['xp']} XP</p>";
    // opis, termin, komunikat, PHP...
    echo '</div>';
}

Zapis {$zadanie['xp']} w cudzysłowie działa tak samo jak {$roznica->days} w komunikatTerminu().

ROZWIĄZANIE

Blok foreach z tej sekcji znajdziesz w kodzie referencyjnym (na końcu panelu). Tam iterujemy już po posortowanej liście — do tego dojdziemy w sekcji 9.

Krok 4 — zostaw $streak i $dataOstatniejAktywnosci

Te dwie zmienne zostają bez zmian. Usuń natomiast:

$zadanieWykonane = true;
$status = $zadanieWykonane ? "Aktywny" : "Nieaktywny";
CO JEST PROBLEMEM?
$zadanieWykonane i $status opisywały stan JEDNEGO zadania.
        ↓
DLACZEGO TO PROBLEM?
Teraz każde zadanie ma własny klucz 'wykonane'. Osobna zmienna byłaby duplikatem,
a $status ("Aktywny" dla wykonanego zadania?) i tak mylił.
        ↓
CO ZMIENIAMY?
$status usuwamy — stan każdego zadania pokazuje znacznik [X] / [ ].
$zadanieWykonane zostaje, ale NIE wpisujemy go już ręcznie —
wyliczymy je z listy (sekcja 10), bo aktualizujStreak() nadal go potrzebuje.

Usuń też (na razie) wywołanie $streak = aktualizujStreak(...) — bez $zadanieWykonane PHP zgłosiłby ostrzeżenie. Wrócimy do niego w sekcji 10. Sama funkcja aktualizujStreak() zostaje w kodzie.

7. Liczymy zadania i sumujemy XP

Ile mamy zadań? Odpowiada count():

$liczbaWszystkich = count($zadania);

A ile XP jest do zdobycia? Musimy przejść po wszystkich elementach i dodać ich 'xp'.

SAMODZIELNIE

Napisz funkcję:

function sumujXp(array $zadania): int

która zwraca sumę wartości 'xp' wszystkich zadań z przekazanej tablicy. Dla pustej tablicy ma zwrócić 0.

Typ array w parametrze działa dokładnie tak samo jak int czy string — mówi, że funkcja oczekuje tablicy.

HINT

Zacznij od zmiennej, która „zbiera” wynik, i dodawaj do niej w każdym obrocie pętli:

$suma = 0;
$suma += 5;   // $suma = $suma + 5
POMOC
function sumujXp(array $zadania): int
{
    $suma = 0;

    foreach ($zadania as $zadanie) {
        // dodaj $zadanie['xp'] do $suma
    }

    return $suma;
}
ROZWIĄZANIE
function sumujXp(array $zadania): int
{
    $suma = 0;

    foreach ($zadania as $zadanie) {
        $suma += $zadanie['xp'];
    }

    return $suma;
}

Wklej ją pod funkcją aktualizujStreak(). Test: sumujXp($zadania) dla listy z lekcji powinno dać 100.

8. Zadania wykonane i aktywne — array_filter()

Użytkownik chce widzieć osobno zadania do zrobienia i te, które ma za sobą. Potrzebujemy filtra: z całej listy wybrać tylko niektóre elementy.

array_filter() bierze tablicę i funkcję decydującą, czy element zostaje.

Minimalny przykład

$liczby = [1, 2, 3, 4, 5, 6];

$parzyste = array_filter($liczby, function ($liczba) {
    return $liczba % 2 === 0;
});

print_r($parzyste);

Wynik:

Array ( [1] => 2  [3] => 4  [5] => 6 )

Co tu się dzieje?

  • function ($liczba) { ... } to funkcja bez nazwy (anonimowa). Nie definiujemy jej osobno — podajemy ją od razu jako drugi argument.
  • PHP wywołuje ją dla każdego elementu tablicy.
  • Jeśli funkcja zwróci true — element zostaje w wyniku, jeśli false — jest pomijany.

Zwróć uwagę na klucze wyniku: 1, 3, 5. array_filter() zachowuje oryginalne klucze, więc w wyniku mogą być „dziury”. Dla foreach nie ma to znaczenia, ale $parzyste[0] nie istnieje!

SAMODZIELNIE

Napisz dwie funkcje:

function pobierzWykonaneZadania(array $zadania): array
function pobierzAktywneZadania(array $zadania): array

Pierwsza zwraca tylko zadania z 'wykonane' => true, druga — tylko z 'wykonane' => false.

HINT

Funkcja decydująca dostaje jeden element (jedno zadanie) i ma zwrócić true lub false. A wartość 'wykonane' jest już wartością true / false.

POMOC
function pobierzWykonaneZadania(array $zadania): array
{
    return array_filter($zadania, function ($zadanie) {
        // zwróć true, jeśli zadanie jest wykonane
    });
}

W drugiej funkcji potrzebujesz zaprzeczenia: !.

ROZWIĄZANIE
function pobierzWykonaneZadania(array $zadania): array
{
    return array_filter($zadania, function ($zadanie) {
        return $zadanie['wykonane'];
    });
}

function pobierzAktywneZadania(array $zadania): array
{
    return array_filter($zadania, function ($zadanie) {
        return !$zadanie['wykonane'];
    });
}

Wklej pod sumujXp(). Następnie (w części „wywołania”, sekcja 10):

$wykonaneZadania = pobierzWykonaneZadania($zadania);
$aktywneZadania = pobierzAktywneZadania($zadania);

$liczbaWykonanych = count($wykonaneZadania);
$liczbaAktywnych = count($aktywneZadania);

9. Sortowanie według terminu — usort()

Lista powinna zaczynać się od zadań, które trzeba zrobić najszybciej. sort() nie wystarczy — elementy to tablice, a my chcemy sortować po jednym z kluczy. Potrzebujemy usort() (sortowanie własną regułą).

Minimalny przykład

$liczby = [30, 10, 20];

usort($liczby, function ($pierwsza, $druga) {
    return $pierwsza <=> $druga;
});

print_r($liczby);   // [10, 20, 30]

Operator <=> („spaceship operator”) porównuje dwie wartości i zwraca:

-1  jeśli lewa jest mniejsza
 0  jeśli są równe
 1  jeśli lewa jest większa

usort() sam wywołuje naszą funkcję dla par elementów i układa tablicę wg wyników. Zmiana kolejności argumentów ($druga <=> $pierwsza) daje sortowanie malejące.

Dwie ważne rzeczy:

  1. usort() zmienia tablicę, na której pracuje (nie zwraca nowej) i numeruje klucze od nowa (0, 1, 2, …).
  2. Daty w formacie RRRR-MM-DD można porównywać jako zwykłe teksty — dlatego sortowanie po 'termin' działa. (To jeden z powodów, dla których zapisujemy daty właśnie tak. Sprawdzisz to w eksperymencie).

SAMODZIELNIE

Napisz funkcję:

function sortujPoTerminie(array $zadania): array

która zwraca zadania ułożone od najwcześniejszego terminu do najpóźniejszego.

HINT

Porównujesz $zadanieA['termin'] z $zadanieB['termin'] — operatorem <=>.

POMOC
function sortujPoTerminie(array $zadania): array
{
    usort($zadania, function ($zadanieA, $zadanieB) {
        // porównaj terminy
    });

    return $zadania;
}

Dlaczego wewnątrz funkcji możemy bezpiecznie sortować? Funkcja dostaje kopię tablicy — oryginalna $zadania z głównego kodu pozostaje nietknięta. Zwracamy posortowaną kopię.

ROZWIĄZANIE
function sortujPoTerminie(array $zadania): array
{
    usort($zadania, function ($zadanieA, $zadanieB) {
        return $zadanieA['termin'] <=> $zadanieB['termin'];
    });

    return $zadania;
}

Wklej pod pobierzAktywneZadania(). W wywołaniach:

$zadaniaWgTerminu = sortujPoTerminie($zadania);
$aktywneWgTerminu = sortujPoTerminie($aktywneZadania);

Teraz w bloku foreach z sekcji 6 zmień $zadania na $zadaniaWgTerminu.

10. Kolejne zadanie — awans i najpilniejsze zadanie

Łączymy nowy temat z tym, co TaskQuest już umie: systemem poziomów.

a) $zadanieWykonane liczymy z listy

aktualizujStreak() potrzebuje informacji, czy użytkownik wykonał zadanie. Zamiast wpisywać true ręcznie, wyliczamy to z listy:

$zadanieWykonane = $liczbaWykonanych > 0;

$streak = aktualizujStreak($streak, $dataOstatniejAktywnosci, $zadanieWykonane);

Uproszczenie (symulacja): zadania nie mają jeszcze daty wykonania, więc na tym etapie uznajemy, że aktywność = „na liście jest choć jedno wykonane zadanie”. Gdy dojdzie baza danych, każde zadanie dostanie datę wykonania i to ona zastąpi to uproszczenie.

Wywołanie aktualizujStreak() przenieś pod linię z $zadanieWykonane — musi ją znać.

SAMODZIELNIE

  1. Oblicz ile XP jest do zdobycia z aktywnych zadań (wykorzystaj sumujXp()) i zapisz w $xpDoZdobycia.
  2. W panelu wyświetl komunikat:
    • jeśli $xpDoZdobycia >= $brakujeXp → „Wykonując aktywne zadania, awansujesz na poziom N!” (N = następny poziom — pamiętasz $poziom + 1?),
    • w przeciwnym razie → „Aktywne zadania nie wystarczą jeszcze na awans — brakuje X XP.”
  3. Wyświetl najpilniejsze zadanie: pierwsze z listy $aktywneWgTerminu (tytuł + komunikat o terminie).
  4. Zadbaj o dwa przypadki brzegowe: lista pusta i wszystkie zadania wykonane. (Wtedy $aktywneWgTerminu[0] nie istnieje!)
HINT
  • $aktywneWgTerminu[0] działa, bo usort() numeruje klucze od zera (a $aktywneZadania[0] — jak pamiętasz — mogłoby nie istnieć).
  • Przypadki brzegowe sprawdzaj przez count() (u nas: $liczbaWszystkich i $liczbaAktywnych).
ROZWIĄZANIE

Zmienna w części wywołań:

$xpDoZdobycia = sumujXp($aktywneZadania);

Fragment panelu HTML (pod statystykami):

<?php
if ($liczbaWszystkich === 0) {
    echo "<p>Brak zadań.</p>";
} elseif ($liczbaAktywnych === 0) {
    echo "<p>Wszystkie zadania wykonane — świetna robota!</p>";
} else {
    if ($xpDoZdobycia >= $brakujeXp) {
        echo "<p class=\"awans\">Wykonując aktywne zadania, awansujesz na poziom " . ($poziom + 1) . "!</p>";
    } else {
        echo "<p>Aktywne zadania nie wystarczą jeszcze na awans — brakuje " . ($brakujeXp - $xpDoZdobycia) . " XP.</p>";
    }

    $najpilniejsze = $aktywneWgTerminu[0];
    echo "<p>Najpilniejsze zadanie: <strong>" . wyczyscTytul($najpilniejsze['tytul']) . "</strong> — "
        . komunikatTerminu($najpilniejsze['termin']) . "</p>";
}
?>

Klasa .awans była w stylach od Lekcji 8 — wreszcie do czegoś służy.

11. Testy

Poniższe wyniki zakładają uruchomienie kodu 18.09.2026. Funkcja komunikatTerminu() porównuje termin z prawdziwą, dzisiejszą datą, więc przy innym dniu komunikaty będą inne. Możesz wtedy przesunąć daty w $zadania o odpowiednią liczbę dni.

Przed uruchomieniem spróbuj przewidzieć wynik.

Test 1 — liczby

Co sprawdzamyOczekiwany wynik
count($zadania)5
$liczbaWykonanych2
$liczbaAktywnych3
sumujXp($zadania)100
sumujXp($aktywneZadania)55
sumujXp($wykonaneZadania)45
sumujXp([])0

Test 2 — kolejność po sortowaniu

ListaKolejność id
$zadaniaWgTerminu2, 3, 1, 5, 4
$aktywneWgTerminu2, 3, 4

Test 3 — klucze (print_r)

TablicaKlucze
$aktywneZadania1, 2, 3
$wykonaneZadania0, 4
$aktywneWgTerminu0, 1, 2

Wyjaśnij, skąd ta różnica między pierwszym a trzecim wierszem.

Test 4 — komunikaty terminów (dla aktywnych zadań)

ZadanieTerminKomunikat
Powtórzyć funkcje2026-09-16Termin minął 2 dni temu!
Ćwiczenia z DateTime2026-09-18Termin jest dzisiaj.
Nauczyć się tablic PHP2026-09-25Do terminu pozostało: 7 dni.

Zadania wykonane nie pokazują komunikatu o terminie.

Test 5 — wartości graniczne awansu

Zmień $xp na wartości z tabeli (aktywne zadania to zawsze 55 XP):

$xp$brakujeXpWynik
0100nie wystarczą — brakuje 45 XP
4456nie wystarczą — brakuje 1 XP
4555awansujesz (dokładnie tyle, ile trzeba!)
991awansujesz

Test 6 — streak

Co sprawdzamyOczekiwany wynik
$streak = 5, ostatnia aktywność 2026-09-14, jest wykonane zadanie1 (przerwa dłuższa niż dzień)
ta sama sytuacja, ale $dataOstatniejAktywnosci = "2026-09-17"6 (aktywność wczoraj)
ta sama sytuacja, ale wszystkie zadania 'wykonane' => false5 (bez zmian)

Test 7 — przypadki brzegowe listy

Zmiana w kodzieOczekiwany efekt
$zadania = [];„Brak zadań.”, liczniki 0/0/0, brak błędów
wszystkie zadania 'wykonane' => true„Wszystkie zadania wykonane — świetna robota!”, brak błędów
dopisz szóste zadanie do $zadaniapojawia się na liście bez zmiany kodu wyświetlania

12. Obowiązkowy eksperyment

Eksperyment 1: dlaczego data w formacie RRRR-MM-DD?

Zmień terminy w $zadania na format polski:

zadanie 1 → '20.09.2026'
zadanie 2 → '16.09.2026'
zadanie 3 → '18.09.2026'
zadanie 4 → '02.10.2026'
zadanie 5 → '22.09.2026'

Uruchom aplikację. Które zadanie jest teraz na początku listy? Czy to poprawne?

Odpowiedz:

Dlaczego zadanie z 2 października wylądowało przed zadaniami z września? Jak porównuje teksty operator <=>? Dlaczego format RRRR-MM-DD tego problemu nie ma?

(Wskazówka: usort() porównuje tutaj teksty znak po znaku, a pierwszy znak '0' jest „mniejszy” niż '1').

Przywróć poprawne daty.

Eksperyment 2: array_filter() a klucze

Wypisz print_r($aktywneZadania) i spróbuj odczytać $aktywneZadania[0]['tytul']. Co się stanie? Dlaczego? Jak zmieni się wynik po użyciu array_values($aktywneZadania)?

Eksperyment 3 (dodatkowy): sortowanie na kopii

Po wywołaniu sortujPoTerminie($zadania) wypisz $zadania[0]['id']. Czy oryginalna lista została zmieniona? Co by się stało, gdybyśmy wywołali usort($zadania, ...) bezpośrednio w głównym kodzie?

Eksperyment 4 (dodatkowy): krótsza wersja sumujXp()

Zapisz sumowanie w jednej linii:

return array_sum(array_column($zadania, 'xp'));

array_column() wyciąga z listy rekordów jedną „kolumnę” (tu: wartości 'xp'), a array_sum() je dodaje. Porównaj wyniki z wersją z foreach.

13. ROZWIĄZANIE — wersja referencyjna

Pełny kod TaskQuest po Lekcji 9. Zwróć uwagę na miejsca oznaczone komentarzem NOWE w Lekcji 9.

Pełny kod
<?php

define('XP_NA_POZIOM', 100);
$imieUzytkownika = "Kama";
$xp = 99;

function obliczPoziom(int $xp) : int {
    $poziom = 1;
    while ($xp >= $poziom * XP_NA_POZIOM) {
        $poziom++;
    }
    return $poziom;
}
$poziom = obliczPoziom($xp);

function pobierzTytul(int $poziom) : string {
    return match ($poziom) {
        1 => "Nowicjusz",
        2 => "Wojownik",
        default => "Mistrz"
    };
}
$tytul = pobierzTytul($poziom);

//postęp wewnątrz aktualnego poziomu
function obliczXpWAktualnymPoziomie(int $xp, int $poziom) : int {
    $xpNaPoczatkuPoziomu = ($poziom - 1) * XP_NA_POZIOM;
    $xpWAktualnymPoziomie = $xp - $xpNaPoczatkuPoziomu;
    return $xpWAktualnymPoziomie;
}
$xpWAktualnymPoziomie = obliczXpWAktualnymPoziomie($xp, $poziom);


function obliczProcentPostepu(int $xpWAktualnymPoziomie) : float {
    return ($xpWAktualnymPoziomie / XP_NA_POZIOM) * 100;
}
$procentPostepu = obliczProcentPostepu($xpWAktualnymPoziomie);

function ileBrakujeXp(int $xp, int $poziom): int
{
    $progNastepnegoPoziomu = $poziom * XP_NA_POZIOM;
    return $progNastepnegoPoziomu - $xp;
}
$brakujeXp = ileBrakujeXp($xp, $poziom);

//pasek postępu
$liczbaAktywnychSegmentow = (int) ($procentPostepu / 10);
$progNastepnegoPoziomu = $poziom * XP_NA_POZIOM;

$login = "Kama_2026";

function walidujLogin(string $login): bool {
    return preg_match('/^[a-zA-Z0-9_-]{3,20}$/', $login) === 1;
}

function wyczyscTytul(string $tytul): string
{
    $tytul = trim(preg_replace('/\s+/', ' ', $tytul));
    return $tytul;
}

function skrocOpis(string $opis, int $limit = 50): string {
    if (mb_strlen($opis) <= $limit) {
        return $opis;
    }
    return mb_substr($opis, 0, $limit) . "...";
}


// --- NOWE w Lekcji 9: lista zadań zamiast pojedynczych zmiennych ---
$zadania = [
    [
        'id' => 1,
        'tytul' => '   Nauczyć   się regexów   PHP   ',
        'opis' => 'Powtórzyć wyrażenia regularne przed następną lekcją.',
        'termin' => '2026-09-20',
        'wykonane' => true,
        'xp' => 20
    ],
    [
        'id' => 2,
        'tytul' => 'Powtórzyć funkcje',
        'opis' => 'Przejrzeć wszystkie funkcje TaskQuest i sprawdzić ich parametry oraz typy zwracanych wartości.',
        'termin' => '2026-09-16',
        'wykonane' => false,
        'xp' => 15
    ],
    [
        'id' => 3,
        'tytul' => 'Ćwiczenia z DateTime',
        'opis' => 'Napisać trzy własne przykłady z diff() i format().',
        'termin' => '2026-09-18',
        'wykonane' => false,
        'xp' => 10
    ],
    [
        'id' => 4,
        'tytul' => 'Nauczyć się tablic PHP',
        'opis' => 'Zbudować listę zadań TaskQuest jako tablicę tablic asocjacyjnych.',
        'termin' => '2026-09-25',
        'wykonane' => false,
        'xp' => 30
    ],
    [
        'id' => 5,
        'tytul' => 'Przygotować projekt do Lekcji 10',
        'opis' => 'Zastanowić się, jak podzielić TaskQuest na pliki i katalogi.',
        'termin' => '2026-09-22',
        'wykonane' => true,
        'xp' => 25
    ]
];

$streak = 5;

$dataOstatniejAktywnosci = "2026-09-14";

function komunikatTerminu(string $terminZadania): string{
    $dzisiaj = new DateTime('today');
    $termin = new DateTime($terminZadania);
    $roznica = $dzisiaj->diff($termin);

    if ($termin < $dzisiaj) {
        return "Termin minął {$roznica->days} dni temu!";
    } elseif ($termin == $dzisiaj) {
        return "Termin jest dzisiaj.";
    } else{
        return "Do terminu pozostało: {$roznica->days} dni.";
    }
}

function aktualizujStreak(int $streak, string $dataOstatniejAktywnosci, bool $zadanieWykonane): int {
    if (!$zadanieWykonane) return $streak;

    $dzisiaj = new DateTime('today');
    $wczoraj = new DateTime('yesterday');
    $ostatniaAktywnosc = new DateTime($dataOstatniejAktywnosci);

    if ($ostatniaAktywnosc->format('Y-m-d') === $dzisiaj->format('Y-m-d')) return $streak;

    if ($ostatniaAktywnosc->format('Y-m-d') === $wczoraj->format('Y-m-d')) return $streak + 1;

    return 1;
}


// --- NOWE w Lekcji 9: funkcje pracujące na liście zadań ---
function pobierzWykonaneZadania(array $zadania): array
{
    return array_filter($zadania, function ($zadanie) {
        return $zadanie['wykonane'];
    });
}

function pobierzAktywneZadania(array $zadania): array
{
    return array_filter($zadania, function ($zadanie) {
        return !$zadanie['wykonane'];
    });
}

function sumujXp(array $zadania): int
{
    $suma = 0;

    foreach ($zadania as $zadanie) {
        $suma += $zadanie['xp'];
    }

    return $suma;
}

function sortujPoTerminie(array $zadania): array
{
    usort($zadania, function ($zadanieA, $zadanieB) {
        return $zadanieA['termin'] <=> $zadanieB['termin'];
    });

    return $zadania;
}


// --- wywołania funkcji dla listy zadań ---
$wykonaneZadania = pobierzWykonaneZadania($zadania);
$aktywneZadania = pobierzAktywneZadania($zadania);

$liczbaWszystkich = count($zadania);
$liczbaWykonanych = count($wykonaneZadania);
$liczbaAktywnych = count($aktywneZadania);

$xpDoZdobycia = sumujXp($aktywneZadania);

$zadaniaWgTerminu = sortujPoTerminie($zadania);
$aktywneWgTerminu = sortujPoTerminie($aktywneZadania);

// uproszczenie (symulacja): aktywność = wykonane jakiekolwiek zadanie z listy
$zadanieWykonane = $liczbaWykonanych > 0;

$streak = aktualizujStreak($streak, $dataOstatniejAktywnosci, $zadanieWykonane);

?>
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TaskQuest</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; }
        .panel { border: 1px solid #ccc; padding: 24px; border-radius: 12px; }
        .pasek-xp { display: flex; gap: 4px; margin: 10px 0; }
        .segment { width: 32px; height: 18px; background: #ddd; border-radius: 3px; }
        .segment.aktywny { background: #555; }
        .awans { font-weight: bold; }
        .zadanie { border: 1px solid #ddd; border-radius: 8px; padding: 4px 16px; margin: 10px 0; }
        .zadanie.wykonane { background: #f2f2f2; color: #666; }
    </style>
</head>
<body>
    <div class="panel">
        <h1>TaskQuest</h1>
        <p>Zdobywaj punkty za wykonane zadania!</p>

        <?php
        if (walidujLogin($login)) {
            echo "<p>Login poprawny: $login</p>";
        } else {
            echo "<p>Login jest niepoprawny.</p>";
        }
        ?>

        <h2>Witaj, <?= $imieUzytkownika; ?>!</h2>

        <p>Poziom: <strong><?= $poziom; ?></strong></p>
        <p>Tytuł: <strong><?= $tytul; ?></strong></p>
        <p>Łączne XP: <strong><?= $xp; ?></strong></p>

        <h3>
            <?= "Masz $xpWAktualnymPoziomie XP z " . XP_NA_POZIOM . " XP potrzebnych do poziomu " . ($poziom + 1) . " (" . round($procentPostepu, 2) . "%)"; ?>
        </h3>

        <div class="pasek-xp">
            <?php
            for ($segment = 1; $segment <= 10; $segment++) {
                if ($segment <= $liczbaAktywnychSegmentow) {
                    echo "█";
                } else {
                    echo "░";
                }
            }
            ?>
        </div>

        <p>Kolejny próg: <?= $progNastepnegoPoziomu; ?> XP</p>
        <p>Brakuje XP: <strong><?= $brakujeXp; ?></strong></p>
        <p>Streak: <strong><?= $streak ?></strong></p>

        <h2>Twoje zadania</h2>

        <p>
            Wszystkie: <strong><?= $liczbaWszystkich; ?></strong> |
            Wykonane: <strong><?= $liczbaWykonanych; ?></strong> |
            Aktywne: <strong><?= $liczbaAktywnych; ?></strong>
        </p>
        <p>XP do zdobycia z aktywnych zadań: <strong><?= $xpDoZdobycia; ?></strong></p>

        <?php
        if ($liczbaWszystkich === 0) {
            echo "<p>Brak zadań.</p>";
        } elseif ($liczbaAktywnych === 0) {
            echo "<p>Wszystkie zadania wykonane — świetna robota!</p>";
        } else {
            if ($xpDoZdobycia >= $brakujeXp) {
                echo "<p class=\"awans\">Wykonując aktywne zadania, awansujesz na poziom " . ($poziom + 1) . "!</p>";
            } else {
                echo "<p>Aktywne zadania nie wystarczą jeszcze na awans — brakuje " . ($brakujeXp - $xpDoZdobycia) . " XP.</p>";
            }

            $najpilniejsze = $aktywneWgTerminu[0];
            echo "<p>Najpilniejsze zadanie: <strong>" . wyczyscTytul($najpilniejsze['tytul']) . "</strong> — "
                . komunikatTerminu($najpilniejsze['termin']) . "</p>";
        }

        echo "<h3>Lista zadań (wg terminu)</h3>";

        foreach ($zadaniaWgTerminu as $zadanie) {
            $tytulCzysty = wyczyscTytul($zadanie['tytul']);
            $znacznik = $zadanie['wykonane'] ? "[X]" : "[ ]";
            $klasa = $zadanie['wykonane'] ? "zadanie wykonane" : "zadanie";

            echo "<div class=\"$klasa\">";
            echo "<p><strong>$znacznik $tytulCzysty</strong> — {$zadanie['xp']} XP</p>";
            echo "<p>" . skrocOpis($zadanie['opis'], 50) . "</p>";
            echo "<p>Termin: {$zadanie['termin']}";

            if (!$zadanie['wykonane']) {
                echo " — " . komunikatTerminu($zadanie['termin']);
            }

            echo "</p>";

            if (stripos($tytulCzysty, 'PHP') !== false) {
                echo "<p>To zadanie dotyczy PHP.</p>";
            }

            echo "</div>";
        }
        ?>
    </div>
</body>
</html>

14. Co zmieniliśmy w TaskQuest?

Zostaje

$imieUzytkownika, $login
$xp, $poziom, $tytul, $xpWAktualnymPoziomie, $procentPostepu, $brakujeXp,
$liczbaAktywnychSegmentow, $progNastepnegoPoziomu
$streak, $dataOstatniejAktywnosci, $zadanieWykonane
define('XP_NA_POZIOM', 100)

obliczPoziom(), pobierzTytul(), obliczXpWAktualnymPoziomie(), obliczProcentPostepu(),
ileBrakujeXp(), walidujLogin(), wyczyscTytul(), skrocOpis(), komunikatTerminu(), aktualizujStreak()

Dochodzi

$zadania                          // tablica tablic asocjacyjnych
$wykonaneZadania, $aktywneZadania
$liczbaWszystkich, $liczbaWykonanych, $liczbaAktywnych
$zadaniaWgTerminu, $aktywneWgTerminu
$xpDoZdobycia

pobierzWykonaneZadania(), pobierzAktywneZadania(), sumujXp(), sortujPoTerminie()

Nowe klucze zadania: id, tytul, opis, termin, wykonane, xp. Nowe narzędzia PHP: tablice, foreach, count(), array_filter(), usort(), <=>, funkcje anonimowe, print_r().

Zmieniamy

  • $zadanieWykonane — nie wpisujemy true ręcznie, tylko wyliczamy z listy (uproszczenie: choć jedno wykonane zadanie = aktywność),
  • komunikatTerminu() — poprawiona literówka w tekście (logika bez zmian),
  • nagłówek z paskiem postępu — ($poziom + 1) zamiast ++$poziom,
  • wyczyscTytul(), skrocOpis(), komunikatTerminu() i stripos() działają teraz na elementach listy, wewnątrz foreach.

Usuwamy

  • $tytulZadania, $opisZadania, $terminZadania — dane tego zadania są teraz w $zadania (unikamy duplikatu),
  • $status — zastąpiony znacznikiem [X] / [ ] przy każdym zadaniu,
  • $loginUzytkownika (jeśli występowało w Twoim kodzie) — obowiązuje $login.

Co nowego potrafi TaskQuest po tej lekcji?

Po Lekcji 9 TaskQuest przechowuje listę zadań, wyświetla ją posortowaną według terminów, liczy zadania wykonane i aktywne, podsumowuje XP do zdobycia i sprawdza, czy aktywne zadania wystarczą na awans na kolejny poziom.

Do samodzielnego sprawdzenia

[ ] Nadal rozwijam ten sam projekt TaskQuest (nie zaczynam nowego).
[ ] W kodzie nie ma już $loginUzytkownika ani ++$poziom.
[ ] Wiem, czym różni się tablica indeksowana od asocjacyjnej.
[ ] Każde zadanie to tablica asocjacyjna, a $zadania to tablica takich tablic.
[ ] Potrafię wyświetlić całą listę jedną pętlą foreach.
[ ] Dodanie szóstego zadania nie wymaga zmian w kodzie wyświetlania.
[ ] Wiem, do czego służy count().
[ ] Potrafię użyć array_filter() i wiem, co zwraca funkcja decydująca.
[ ] Wiem, dlaczego po array_filter() klucze mogą mieć "dziury".
[ ] Potrafię posortować tablicę rekordów przez usort() i <=>.
[ ] Zachowałem funkcje z Lekcji 7 i 8 i używam ich na elementach listy.
[ ] Przetestowałem pustą listę i listę, w której wszystkie zadania są wykonane.

Pytania kontrolne

  1. Dlaczego zestaw zmiennych $tytulZadania, $terminZadania… przestał wystarczać?
  2. Czym różni się $xpZaDni[0] od $zadanie['xp']?
  3. Co wypisze echo $zadania[1]['tytul']; dla listy z lekcji? (Przewidź, potem sprawdź.)
  4. W jakiej sytuacji foreach nie wypisze niczego — i dlaczego to nie jest błąd?
  5. Co zwraca funkcja podana do array_filter()? Co dzieje się z elementem, dla którego zwróciła false?
  6. Dlaczego $aktywneZadania[0] może nie istnieć, a $aktywneWgTerminu[0] istnieje?
  7. Co oznacza wynik -1 z operatora <=>? Jak zamienić sortowanie rosnące na malejące?
  8. Dlaczego wewnątrz sortujPoTerminie() możemy sortować, a oryginalna $zadania się nie zmienia?
  9. Dlaczego w Lekcji 9 nie usuwamy komunikatTerminu() i aktualizujStreak(), choć zmienia się sposób przechowywania zadań?
  10. $tytul i $zadanie['tytul'] — czym się różnią w TaskQuest?

Zadanie końcowe — rozbuduj TaskQuest samodzielnie

Wybierz jedno (albo oba):

A) Sortowanie według XP. Dopisz funkcję sortujPoXp(array $zadania): array, która układa zadania od najwyżej do najniżej punktowanych (pamiętasz, jak zamienić kolejność w <=>?). Wyświetl w panelu informację „Największa nagroda: …” — czyli tytuł i XP pierwszego zadania z posortowanej listy.

B) Pierwszy ranking graczy. Utwórz tablicę $ranking z co najmniej trzema graczami. Każdy gracz ma klucze 'login' i 'xp'. Wpisz tam siebie ($login, $xp) i dwóch „rywali”. Posortuj ranking malejąco po XP i wyświetl go w foreach z numerami miejsc.

Pamiętaj: nazwa klucza to 'login' — nie 'loginUzytkownika'.

Most do Lekcji 10

Plik TaskQuest ma już kilkaset linii: funkcje, dane, style i HTML w jednym miejscu. Coraz trudniej w nim cokolwiek znaleźć.

Następny problem:

Jak podzielić TaskQuest na osobne pliki i katalogi, żeby aplikacja wciąż działała tak samo?


PODSUMOWANIE LEKCJI

W skrócie

  • Tablica to jedna zmienna z wieloma wartościami. Każda wartość ma klucz (indeks) i wartość.
  • Tablica indeksowana: klucze to liczby od 0. Pierwszy element to [0], ostatni to [count($tab) - 1].
  • Tablica asocjacyjna: klucze to nazwy ('tytul', 'xp'). Opisuje jedną „rzecz” – np. jedno zadanie.
  • Tablica tablic (lista rekordów) = lista zadań. Odczyt: najpierw numer elementu, potem klucz: $zadania[1]['tytul'].
  • $tab[] = wartosc dopisuje na końcu z kluczem o 1 większym od największego klucza liczbowego (nie „kolejnym wolnym”).
  • Odczyt nieistniejącego klucza nie zatrzymuje skryptu, ale daje ostrzeżenie Undefined array key i wartość null.
  • foreach ($zadania as $zadanie) – $zadanie to kopia elementu. Zmiana $zadanie['wykonane'] w pętli nie zmienia $zadania.
  • Kod z foreach nie zależy od liczby elementów – dla pustej tablicy pętla po prostu się nie wykona (to nie jest błąd).
  • W cudzysłowie klucz tablicy zapisujemy w klamrach: "{$zadanie['xp']} XP". Bez klamer ("$zadanie['xp']") jest błąd składni.
  • array_filter() zachowuje oryginalne klucze → w wyniku mogą być „dziury” ([1], [3]…), a [0] może nie istnieć. Numerację od zera przywraca array_values().
  • Funkcja podana do array_filter() zwraca true (element zostaje) albo false (element odpada).
  • usort() zmienia tablicę, którą dostaje, numeruje klucze od nowa i zwraca true – nie zwraca posortowanej tablicy.
  • Funkcja porównująca w usort() zwraca wynik <=>: $a <=> $b – rosnąco, $b <=> $a – malejąco.
  • sort() / rsort() gubią klucze – nie używaj ich na tablicy asocjacyjnej, jeśli klucze są ważne (wtedy asort() / arsort() / ksort()).
  • Tablica przekazana do funkcji to kopia – dlatego sortujPoTerminie() może sortować, a oryginał $zadania zostaje bez zmian.
  • Daty w formacie RRRR-MM-DD sortują się poprawnie jako tekst. Daty DD.MM.RRRR – nie.
  • W TaskQuest: $tytul to tytuł gracza, $zadanie['tytul'] to tytuł zadania.

Co trzeba umieć na INF.03?

  • odczytać element tablicy indeksowanej i asocjacyjnej ($wiersz[1], $wiersz['nazwa']),
  • odczytać wartość z tablicy tablic ($zadania[2]['termin']),
  • wyświetlić wszystkie elementy tablicy w pętli foreach, wstawiając je w HTML (akapity, <li>, wiersze <tr>, <option>),
  • wstawić element tablicy do tekstu w cudzysłowie ({$...}) lub przez konkatenację,
  • podać wartość domyślną dla brakującego klucza: $_GET['id'] ?? 6 (albo isset()),
  • sprawdzić liczbę elementów (count()) i obsłużyć pustą tablicę,
  • podejrzeć zawartość tablicy (print_r() w <pre>) podczas szukania błędu.

Minimum, które powinno wejść w pamięć

  1. Indeksy zaczynają się od 0.
  2. $tab[] = x; – dopisz na końcu. count($tab) – ile elementów.
  3. Lista rekordów: $zadania[numer]['klucz'].
  4. foreach ($tablica as $element) oraz foreach ($tablica as $klucz => $wartosc).
  5. W cudzysłowie: "{$zadanie['xp']}" – z klamrami.
  6. print_r() w <pre> – gdy nie wiesz, co jest w tablicy.
  7. array_filter() zachowuje klucze → uważaj na [0]; array_values() numeruje od nowa.
  8. usort($tab, funkcja) sortuje w miejscu; $a <=> $b rosnąco, $b <=> $a malejąco.
  9. sort() gubi klucze, asort() je zachowuje.
  10. Brakujący klucz → ?? wartośćDomyślna.

Ściągawka – najważniejsze polecenia i składnia

Tworzenie i odczyt

ZapisZnaczenie
$t = [];pusta tablica
$t = [10, 25, 5];indeksowana (klucze 0, 1, 2)
$z = ['tytul' => 'PHP', 'xp' => 20];asocjacyjna
$lista = [ [...], [...] ];tablica tablic
$t[0], $z['xp'], $lista[1]['xp']odczyt
$t[] = 40;dopisanie na końcu
unset($t[1]);usunięcie elementu (klucze się nie przesuwają)

Informacje i wyszukiwanie

FunkcjaZwraca
count($t)liczbę elementów
in_array('x', $t)true/false – czy jest taka wartość
isset($z['xp']) / array_key_exists('xp', $z)czy jest taki klucz
$z['opis'] ?? 'brak'wartość albo domyślną
array_sum(array_column($lista, 'xp'))suma jednej „kolumny” listy
implode(', ', $t) / explode(',', $tekst)tablica → tekst / tekst → tablica

Pętle

foreach ($zadania as $zadanie) {
    echo "<li>{$zadanie['tytul']} — {$zadanie['xp']} XP</li>";
}

foreach ($zadanie as $klucz => $wartosc) {
    echo "$klucz: $wartosc<br>";
}

Filtrowanie

$wynik = array_filter($tablica, function ($element) {
    return /* warunek: true = zostaje */;
});
$wynik = array_values($wynik);   // opcjonalnie: klucze od 0

Sortowanie

usort($tablica, function ($a, $b) {
    return $a['klucz'] <=> $b['klucz'];   // rosnąco; $b <=> $a – malejąco
});
<=> zwracagdy
-1lewa < prawa
0równe
1lewa > prawa
FunkcjaSortuje poKlucze
sort() / rsort()wartościach ↑ / ↓tracone
asort() / arsort()wartościach ↑ / ↓zachowane
ksort() / krsort()kluczach ↑ / ↓zachowane
usort()własna regułatracone

Podgląd

echo '<pre>';
print_r($zadania);
echo '</pre>';

Quiz – sprawdź się

1. Co zostanie wyświetlone?

$kolory = ['czerwony', 'zielony', 'niebieski'];
echo $kolory[1] . " " . count($kolory);

2. Jakie klucze będą miały elementy 'b' i 'd'?

$t = [5 => 'a'];
$t[] = 'b';
$t[2] = 'c';
$t[] = 'd';

3. Co zostanie wyświetlone?

$gracze = [
    ['login' => 'Kama_2026', 'xp' => 99],
    ['login' => 'Adam_12',   'xp' => 40],
    ['login' => 'Ola-7',     'xp' => 75]
];
echo $gracze[2]['login'] . " ma " . $gracze[0]['xp'] . " XP";

4. Ile wynosi count($macierz)?

$macierz = [[1, 2], [3, 4, 5]];

5. Znajdź błąd i popraw linię.

echo "<p>{$zadanie['tytul']} — $zadanie['xp'] XP</p>";

6. Co wypisze print_r($parzyste)? Co stanie się po echo $parzyste[0];? Jak to naprawić?

$liczby = [3, 8, 5, 10];
$parzyste = array_filter($liczby, function ($liczba) {
    return $liczba % 2 === 0;
});

7. Uzupełnij kod tak, aby $cenne zawierało tylko zadania warte co najmniej 20 XP.

$cenne = array_filter($zadania, function ($zadanie) {
    return ______________;
});

8. Jaki jest wynik każdego porównania?

echo 5 <=> 3;
echo 2 <=> 2;
echo 'a' <=> 'b';

9. Która wersja ułoży zadania od najwyżej do najniżej punktowanego?

A.

usort($zadania, function ($a, $b) {
    return $a['xp'] <=> $b['xp'];
});

B.

usort($zadania, function ($a, $b) {
    return $b['xp'] <=> $a['xp'];
});

C.

sort($zadania);

10. Dlaczego ten kod nie wyświetla listy (PHP zgłasza ostrzeżenie przy foreach)?

$posortowane = usort($zadania, function ($a, $b) {
    return $a['termin'] <=> $b['termin'];
});

foreach ($posortowane as $zadanie) {
    echo $zadanie['tytul'] . "<br>";
}

11. Lista $zadania z lekcji ma 3 aktywne zadania. Co wyświetli ten kod i dlaczego?

foreach ($zadania as $zadanie) {
    $zadanie['wykonane'] = true;
}
echo count(pobierzAktywneZadania($zadania));

12. Co wyświetli kod i które z dwóch zadań zostanie przez to uznane za pilniejsze przy sortowaniu usort() po terminie? Czy to poprawne?

echo '02.10.2026' <=> '16.09.2026';

13. Co wypisze print_r($oceny)? Której funkcji użyć, żeby posortować oceny malejąco i nie stracić imion?

$oceny = ['Jan' => 4, 'Anna' => 5, 'Piotr' => 3];
sort($oceny);

14. Wiersz z bazy danych trafił do skryptu jako tablica numeryczna:

$wiersz = [34, 'Polska', 'Siklawica', 70];

Zapisz jedną instrukcję echo, która wyświetli: <tr><td>Siklawica</td><td>70</td></tr>

15. Zadanie praktyczne. W TaskQuest pod listą zadań wyświetl nagłówek Ukończone i listę <ul> z tytułami (oczyszczonymi) tylko wykonanych zadań. Wykorzystaj funkcje, które już masz. Gdy żadne zadanie nie jest wykonane, zamiast listy wyświetl Nic jeszcze nie ukończono.


Odpowiedzi
  1. zielony 3
  2. 'b' → klucz 6, 'd' → klucz 7 (nowy klucz = największy dotychczasowy + 1, a nie „pierwszy wolny”).
  3. Ola-7 ma 99 XP
  4. 2 – count() liczy elementy tylko pierwszego poziomu.
  5. W cudzysłowie klucz z apostrofami wymaga klamer: echo "<p>{$zadanie['tytul']} — {$zadanie['xp']} XP</p>"; (albo konkatenacja).
  6. Array ( [1] => 8 [3] => 10 ). $parzyste[0] nie istnieje – ostrzeżenie Undefined array key 0, nic się nie wyświetli. Naprawa: $parzyste = array_values($parzyste); – wtedy klucze to 0 i 1.
  7. return $zadanie['xp'] >= 20;
  8. 1, 0, -1
  9. B. A sortuje rosnąco. C – sort() nie wie, po którym kluczu rekordu sortować.
  10. usort() sortuje tablicę $zadania w miejscu i zwraca true, a nie posortowaną tablicę. $posortowane to true, po którym nie da się iterować. Poprawnie: wywołać usort($zadania, ...) i iterować po $zadania (albo użyć sortujPoTerminie(), która zwraca posortowaną kopię).
  11. 3 – $zadanie w foreach to kopia elementu, więc zmiana nie trafia do $zadania.
  12. -1 – teksty są porównywane znak po znaku, a '0' < '1'. Zadanie z 2 października trafi przed zadanie z 16 września – to błąd. W formacie RRRR-MM-DD najpierw porównywany jest rok, potem miesiąc, potem dzień, więc kolejność tekstowa zgadza się z kalendarzową.
  13. Array ( [0] => 3 [1] => 4 [2] => 5 ) – imiona zniknęły, bo sort() nadaje nowe klucze. Malejąco z zachowaniem kluczy: arsort($oceny);
  14. Na przykład: echo "<tr><td>{$wiersz[2]}</td><td>{$wiersz[3]}</td></tr>"; albo echo "<tr><td>" . $wiersz[2] . "</td><td>" . $wiersz[3] . "</td></tr>";
  15. Przykładowe rozwiązanie:
<h3>Ukończone</h3>
<?php
if (count($wykonaneZadania) === 0) {
    echo "<p>Nic jeszcze nie ukończono.</p>";
} else {
    echo "<ul>";
    foreach ($wykonaneZadania as $zadanie) {
        echo "<li>" . wyczyscTytul($zadanie['tytul']) . "</li>";
    }
    echo "</ul>";
}
?>

Sprawdzenie: dla listy z lekcji – 2 pozycje (Nauczyć się regexów PHP, Przygotować projekt do Lekcji 10); po ustawieniu wszystkich 'wykonane' => false – komunikat „Nic jeszcze nie ukończono.”, bez ostrzeżeń. foreach działa poprawnie mimo „dziur” w kluczach $wykonaneZadania.