8. PHP – Łańcuchy znaków i wyrażenia regularne.


Szybki quiz – przypomnienie poprzedniej lekcji

1. Co wyświetli kod?

echo date("d.m.Y", mktime(0, 0, 0, 13, 1, 2024));

2. Który format daje wynik 05.09.2026 dla 5 września 2026?

  • A) j.n.Y
  • B) d.m.Y
  • C) d.m.y
  • D) D.M.Y

3. Co wyświetli kod?

$a = new DateTime('2026-09-01');
$b = $a->modify('+1 day');
echo $a->format('Y-m-d');

Jak zmienić pierwszą linię, aby $a pozostało bez zmian?

4. Co wyświetli kod?

$roznica = (new DateTime('2026-09-01'))->diff(new DateTime('2026-09-25'));
echo $roznica->days;

5. Co wyświetli kod?

echo date("Y-m-d", strtotime("+7 days", strtotime("2026-09-25")));

6. Dlaczego ten kod kończy się błędem?

$terminZadania = "2026-09-20";
echo $terminZadania->format('d.m.Y');

7. W funkcji komunikatTerminu() użyliśmy new DateTime('today'), a nie new DateTime('now'). Co przestałoby działać, gdybyśmy użyli now?

8. aktualizujStreak() otrzymuje $streak = 5 i $zadanieWykonane = true. Jaki będzie wynik, jeżeli ostatnia aktywność była:

  • a) wczoraj,
  • b) dzisiaj,
  • c) 4 dni temu?
Odpowiedzi
  1. 01.01.2025 – mktime() koryguje miesiąc 13 na styczeń następnego roku.
  2. B – d i m z zerami wiodącymi, Y – rok 4-cyfrowy.
  3. 2026-09-02 – modify() zmienia oryginalny obiekt, a $b wskazuje ten sam obiekt. Poprawka: $a = new DateTimeImmutable('2026-09-01');.
  4. 24.
  5. 2026-10-02.
  6. $terminZadania jest tekstem, a nie obiektem – nie ma metody format(). Trzeba utworzyć obiekt: $termin = new DateTime($terminZadania);.
  7. now zawiera aktualną godzinę, a termin zapisany jako sama data ma godzinę 00:00. Warunek „termin jest dzisiaj” (==) nigdy nie byłby spełniony, a termin przypadający dzisiaj byłby traktowany jak miniony.
  8. a) 6, b) 5, c) 1.

Spis zagadnień:

  1. Łańcuchy znaków
    1. echo/print
    2. addslashes/stripslashes
    3. explode/implode
    4. str_split
    5. htmlspecialchars/strip_tags
    6. nl2br
    7. password_hash
    8. strlen
    9. strpos
    10. wielkość liter
    11. str_replace
    12. substr
    13. substr_replace
    14. strstr
    15. strtr
    16. trim
    17. przykłady
    18. zadania do wykonania
  2. Wyrażenia regularne
  3. TaskQuest
  4. Dodatek

 

Łańcuchy znaków

Łańcuch znaków (ciąg), to typ danych reprezentujący tekst, które możemy formatować za pomocą wielu funkcji wbudowanych języka PHP.

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

Sposoby definiowania stringów:

// Podwójne cudzysłowy - interpretują zmienne i znaki specjalne
$imie = "Jan";
echo "Witaj, $imie!"; // Witaj, Jan!
echo "Nowa linia\n"; // \n zostanie zamienione na nowy wiersz

// Pojedyncze cudzysłowy - literalnie, bez interpretacji
echo 'Witaj, $imie!'; // Witaj, $imie!
echo 'Nowa linia\n'; // Nowa linia\n (dosłownie)

// Heredoc (jak podwójne cudzysłowy)
$tekst = <<<EOT
To jest wieloliniowy
tekst z interpretacją $imie
EOT;

// Nowdoc - PHP 5.3+ (jak pojedyncze cudzysłowy)
$tekst = <<<'EOT'
To jest wieloliniowy
tekst bez interpretacji $imie
EOT;

 

echo/print

Wyświetlanie treści na ekranie.

Różnice:

  • echo – szybsze, może przyjąć wiele argumentów, nie zwraca wartości
  • print – wolniejsze, jeden argument, zwraca 1
<?php 
echo "Hello World!"; // wyświetli napis: Hello World!
print "Hello World!"; // alternatywne wyświetlanie: Hello World!
echo "<br>"; //w ten sposób dodajemy wyświetlanie nowej linii

//wyświetlanie wielu linii
echo "Ten zapis obejmuje wiele linii.
Będą one wyświetlane, ale w jednej linii.
Nie zostanie automatycznie dodany znacznik nowego wiersza.";

echo "<br>Ten zapis również obejmuje wiele linii.<br>
Będą one wyświetlane, w wielu liniach,<br>
przez dodanie znacznika nowego wiersza.";

echo "<br>";

//wyświetlanie znaków specjalnych tj. "" musimy poprzedzić backslash'em
echo "Jeżeli chcemy wyświetlić cudzysłów \"Przykład\".";

echo "<br>";

// Wyświetlanie zawartości zmiennych
$zmienna1 = "wartość zmiennej 1";
$zmienna2 = "wartość zmiennej 2";
echo "zmienna1 = $zmienna1, a zmienna2 = $zmienna2"; // foo is foobar

echo "<br>";

// używając apostrofu wyświetlamy dokładnie to co znajduje się pomiędzy apostrofami (nie pobiera zawartości zmiennej)
echo 'wartość zmiennej 1 znajduje się w $zmienna1'; // wartość zmiennej 1 znajduje się w $zmienna1

echo "<br>";

// jeżeli wyświetlamy samą zmienną nie musimy używać cudzysłowów, ani apostrofów
echo $zmienna1; // wartość zmiennej 1

echo "<br>";

//łączenie wielu elementów do wyświetlenia
echo $zmienna1.$zmienna2."<br>"; // za pomocą kropki dołączamy kolejne zmienne
echo $zmienna1,$zmienna2."<br>"; // przecinek działa podobnie, nie jest używany - stosujemy kropkę


// Some people prefer passing multiple parameters to echo over concatenation.
echo 'Można ', 'łączyć ', 'wiele ', 'elementów ', ' oraz dodawać zmienne ', $zmienna1, '<br>';
echo 'Można '. 'łączyć '. 'wiele '. 'elementów '. ' oraz dodawać zmienne '. $zmienna1. '<br>';

//alternatywna metoda wyświetlania. Działa pod warunkiem, iż łańcuch rozpocznie się i zakończy tym samym słowem, w tym wypadku END. Nie jest to jednak metoda często spotykana.
echo <<<END
Ten tekst może być wielolinijkowy w wedytorze.
Niestety w przeglądarce zostanie wyświetlony w jednej linii.
END;

echo "<br>";

// wyświetlanie tablicowe
$tablica = ["indeks" => "wartość"];
echo "wartość dla indeksu = {$tablica['indeks']} !"; // wyświetli: wartość dla indeksu = wartość !

echo "<br>";

// wyświetlanie z warunkiem jeżeli (warunek) ? 'wartość gdy prawda' : 'wartość gdy fałsz'
echo ($zmienna1) ? 'true' : 'false';

?>

//Krótka składnia w HTML (short echo tag)
<p>użycie PHP w HTML <?php echo $zmienna1; ?></p>
<p>użycie PHP w HTML w skróconej formie <?=$zmienna1;?></p>


addslashes()/stripslashes()

Funkcja addslashes() dodaje backslash \ do ciągu znaków zawsze wtedy, gdy natrafi na tzw. „znak ucieczki”.

<?php
$text = "Muszę zapisać znak ', czyli apostrof.";
echo addslashes($text); 
//zostanie wyświetlone: Muszę zapisać znak \', czyli apostrof. 
?>

Funkcja stripslashes() usuwa backslash

<?php 
$text = "Muszę odczytać z bazy znak \', czyli usunąć backslash."; 
echo stripslashes($text); 
//zostanie wyświetlone: Muszę zapisać znak ', czyli usunąć backslash. 
?>

explode() / implode() / join()

PHP umożliwia zamianę ciągów tekstowych na tablice i odwrotnie. Zamiana ciągu na tablicę jest bardzo przydatna jeśli zachodzi potrzeba „wyciągnięcia” jakiegoś fragmentu danych z ciągu. Załóżmy że w odczytaliśmy z pliku z danymi (o odczycie z plików w jednym z kolejnych rozdziałów) linię z logu zapisanego przez licznik WWW: „12/11/2000;19:23:33;Netscape Navigator;192.168.1.1”. Jak widać dane rozdzielone są średnikami. Do rozdzielania ciągów na tablicę służy funkcja explode(). Jako pierwszy parametr trzeba do niej podać znak lub dłuższy ciąg który oddziela kolejne pola, jako drugi ciąg do rozdzielenia. Opcjonalnie można podać trzeci argument, który oznacza maksymalną liczbę pól – jeśli jest ich więcej niż ta liczba, to ostatnie pole będzie zawierało wszystkie pozostałe pola. Funkcja zwraca tablicę zawierającą kolejne pola.
Przykładowo:

<?php 
$dane = "alfa;beta;gamma;delta";
$tablica = explode(";", $dane);
print_r($tablica);
// Array([0] => alfa, [1] => beta, [2] => gamma, [3] => delta)

// Z limitem
$tablica = explode(";", $dane, 2);
print_r($tablica);
// Array([0] => alfa, [1] => beta;gamma;delta)

// Praktyczny przykład - parsowanie CSV
$csv = "Jan,Kowalski,30,Warszawa";
list($imie, $nazwisko, $wiek, $miasto) = explode(",", $csv);
echo "Imię: $imie, Nazwisko: $nazwisko";
?>

 

Czasem potrzebne jest działanie pozwalające na złącznie pól tablicy w jeden ciąg, w którym pola oddzielone są jakimś znakiem (lub kilkoma). Do tego służy funkcja implode() i join(). Jako pierwszy parametr podawany jest ciąg za pomocą którego „sklejane” są elementy tablicy, a jako drugi właśnie tablica do posklejania. Zwracany jest ciąg zawierający „posklejane” elementy. Przykład zastosowania:

<?php 
$tablica = ['alfa', 'beta', 'gamma'];
$dane = implode(";", $tablica);
echo $dane; // alfa;beta;gamma

// Praktyczny przykład - tworzenie listy
$owoce = ['jabłko', 'gruszka', 'banan'];
echo "Mamy: " . implode(", ", $owoce); // Mamy: jabłko, gruszka, banan

// join() to alias implode() - możesz używać obu
$dane = join(";", $tablica);
?>

str_split()

Dzieli string na tablicę znaków lub fragmentów.

array str_split(string $string [, int $length = 1])

<?php
$str = "Hello Friend";

// Podział na pojedyncze znaki
$arr1 = str_split($str);
print_r($arr1);
// Array([0] => H, [1] => e, [2] => l, [3] => l, [4] => o, ...)

// Podział na fragmenty długości 3
$arr2 = str_split($str, 3);
print_r($arr2);
// Array([0] => Hel, [1] => lo , [2] => Fri, [3] => end)

// Praktyczny przykład - podział numeru karty
$karta = "1234567890123456";
$chunked = str_split($karta, 4);
echo implode("-", $chunked); // 1234-5678-9012-3456
?>

 htmlspecialchars()/strip_tags()

Zabezpieczanie przed XSS (Cross-Site Scripting).

<?php

$niebezpieczny = '<script>alert("XSS")</script>';
echo htmlspecialchars($niebezpieczny);
// &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Praktyczny przykład - wyświetlanie inputa użytkownika
$komentarz = $_POST['komentarz'] ?? '';
echo htmlspecialchars($komentarz, ENT_QUOTES, 'UTF-8');

?>

htmlspecialchars() – koduje znaki specjalne HTML:

  • & → &amp;
  • ” → &quot;
  • ’ → &#039; (lub &apos;)
  • < → &lt;
  • > → &gt;

Flagi:

ENT_QUOTES      // Koduj i podwójne, i pojedyncze cudzysłowy
ENT_NOQUOTES    // Nie koduj cudzysłowów
ENT_SUBSTITUTE  // Zastąp nieprawidłowe sekwencje znakiem �
ENT_HTML5       // Obsługa HTML5

strip_tags() – usuwa tagi HTML/PHP:

$html = '<p>Cześć <b>świecie</b>!</p>';
echo strip_tags($html); // Cześć świecie!

// Dozwolone tagi
echo strip_tags($html, '<b>'); // Cześć <b>świecie</b>!

// PHP 7.4+ - tablica dozwolonych tagów
echo strip_tags($html, ['p', 'b']); // <p>Cześć <b>świecie</b>!</p>

Bezpieczeństwo:

// ŹLE - strip_tags nie chroni przed XSS!
$input = '<img src=x onerror=alert("XSS")>';
echo strip_tags($input); // Może wykonać JavaScript!

// DOBRZE - używaj htmlspecialchars
echo htmlspecialchars($input);

nl2br()

Funkcja zamienia wszystkie znaki \n na znacznik HTML <br />

<?php
$tekst = "Linia 1\nLinia 2\nLinia 3";
echo nl2br($tekst);
// Linia 1<br />
// Linia 2<br />
// Linia 3

// HTML5 (bez XHTML)
echo nl2br($tekst, false);
// Linia 1<br>
// Linia 2<br>
// Linia 3
?>

password_hash() / password_verify()

password_hash() – hashuje hasło:

Bezpieczne hashowanie haseł 

<?php
// Podstawowe użycie (zalecane)
$haslo = 'moje_super_haslo';
$hash = password_hash($haslo, PASSWORD_DEFAULT);
echo $hash;
// $2y$10$... (hash bcrypt)

// PHP 7.4+ - Argon2
$hash = password_hash($haslo, PASSWORD_ARGON2ID);

// Własne ustawienia (opcjonalne)
$opcje = ['cost' => 12]; // wyższy cost = wolniejsze, bezpieczniejsze
$hash = password_hash($haslo, PASSWORD_BCRYPT, $opcje);
?>

 

password_verify()

Jest to funkcja sprawdzająca czy zahashowane hasła zgadzają się z podanymi.

<?php
$haslo = 'moje_super_haslo';
$hash = '$2y$10$...'; // hash z bazy danych

if (password_verify($haslo, $hash)) {
echo "Hasło poprawne!";
} else {
echo "Błędne hasło!";
}
?>

strlen() / mb_strlen()

Zwraca długość stringa (liczbę bajtów).

<?php
$str = 'abcdef';
echo strlen($str); // 6

$str = ' ab cd ';
echo strlen($str); // 7

// Uwaga na polskie znaki (UTF-8)
$str = 'ąćęł';
echo strlen($str); // 8 (2 bajty na znak w UTF-8!)

// Dla znaków Unicode - użyj mb_strlen()
echo mb_strlen($str, 'UTF-8'); // 4 (poprawna liczba znaków)

// Dla stringów z polskimi znakami
$tekst = "Zażółć gęślą jaźń";
echo mb_strlen($tekst, 'UTF-8'); // 17 znaków
?>

strpos() / mb_strpos()

Zwraca numeryczną pozycję pierwszego wystąpienia szukanego znaku w ciągu. Jako trzeci parametr, można ustawić numer znaku, od którego funkcja ma zacząć przeszukiwać.

strpos(string,find[,start])

<?php
$tekst = "I love PHP, I love PHP too!";

// Pierwsze wystąpienie
echo strpos($tekst, "PHP"); // 7

// Od określonej pozycji
echo strpos($tekst, "PHP", 8); // 19

// Nie znaleziono
var_dump(strpos($tekst, "Java")); // false

// Uwaga na pozycję 0!
if (strpos($tekst, "I") !== false) { // użyj !== zamiast !=
echo "Znaleziono!";
}
?>

str_contains(), str_starts_with(), str_ends_with()

// Sprawdza czy string zawiera podciąg
if (str_contains($tekst, "PHP")) {
    echo "Zawiera PHP";
}

// Sprawdza czy zaczyna się od
if (str_starts_with($tekst, "I love")) {
    echo "Zaczyna się od 'I love'";
}

// Sprawdza czy kończy się na
if (str_ends_with($tekst, "too!")) {
    echo "Kończy się na 'too!'";
}

 

strtolower()/strtoupper()/ucfirst()/ucwords()

Funkcje odpowiedzialne za zmianę wielkości liter.

<?php
// strtolower() - wszystkie małe
$str = "Marysia Ma Małego Baranka";
echo strtolower($str); // marysia ma małego baranka

// strtoupper() - wszystkie wielkie
echo strtoupper($str); // MARYSIA MA MAŁEGO BARANKA

// ucfirst() - pierwsza litera wielka
echo ucfirst(strtolower($str)); // Marysia ma małego baranka

// ucwords() - każde słowo wielką literą
echo ucwords(strtolower($str)); // Marysia Ma Małego Baranka

// mb_strtolower() / mb_strtoupper() - dla polskich znaków
$polski = "ZAŻÓŁĆ GĘŚLĄ JAŹŃ";
echo mb_strtolower($polski, 'UTF-8'); // zażółć gęślą jaźń
echo mb_strtoupper("zażółć", 'UTF-8'); // ZAŻÓŁĆ
?>

str_replace() / str_ireplace()

Zamienia wszystkie wystąpienia podciągu.. Budowa:

str_replace(znajdź, zastąp, string[, $jakaś_nazwa]), gdzie do opcjonalnej zmiennej $jakaś_nazwa zostanie wpisana ilość zmian jakich dokona funkcja

<?php

// Podstawowe użycie
$tekst = "Jeśli jesteś za, podnieś rękę";
$wynik = str_replace("za", "przeciw", $tekst);
echo $wynik; // Jeśli jesteś przeciw, podnieś rękę

// Licznik zamian
$wynik = str_replace("za", "przeciw", $tekst, $count);
echo "Zmieniono: $count"; // Zmieniono: 1

// Wiele zamian naraz
$tekst = "czerwony niebieski czerwony";
$wynik = str_replace(
["czerwony", "niebieski"],
["różowy", "zielony"],
$tekst
);
echo $wynik; // różowy zielony różowy

// Operacje na tablicach
$arr = ["blue", "red", "blue", "yellow"];
print_r(str_replace("blue", "pink", $arr));
// Array([0] => pink, [1] => red, [2] => pink, [3] => yellow)

?>

 str_ireplace() – zamiana bez względu na wielkość liter:

$tekst = "PHP php Php pHP";
echo str_ireplace("php", "JavaScript", $tekst);
// JavaScript JavaScript JavaScript JavaScript

substr()

Funkcja substr() wycina fragment ciągu wg podanych parametrów. Budowa:

substr(string, początek[, długość])

jeżeli:

początek>0 – wycinanie zacznie się od znaku o podanym numerze

początek<0 – wycinanie zacznie się od miejsca = długość-początek

długość>0 – wycinanie zacznie się od podanej cyfry określającej początek do początek+długość

długość<0 – wycinanie zakończy się o podaną liczbę znaków przed końcem

<?php
echo substr("abcdef", -1); // zwraca "f"
echo substr("abcdef", -2); // zwraca "ef"
echo substr("abcdef", -3, 1); // zwraca "d"
echo substr("abcdef", 0, -1); // zwraca "abcde"
echo substr("abcdef", 2, -1); // zwraca "cde"
echo substr("abcdef", 4, -4); // zwraca false
echo substr("abcdef", -3, -1); // zwraca "de"
echo substr('abcdef', 1); // bcdef
echo substr('abcdef', 1, 3); // bcd
echo substr('abcdef', 0, 4); // abcd
echo substr('abcdef', 0, 8); // abcdef
echo substr('abcdef', -1, 1); // f

//metoda zapisu dla tablic
$string = 'abcdef';
echo $string[0]; // a
echo $string[3]; // d
echo $string[strlen($string)-1]; // f

//mb_substr() - dla znaków Unicode:
$polski = "Zażółć gęślą jaźń";
echo mb_substr($polski, 0, 6, 'UTF-8'); // Zażółć


// Praktyczny przykład - skrócenie tekstu
function skroc($tekst, $dlugosc = 100) {
if (strlen($tekst) <= $dlugosc) {
return $tekst;
}
return substr($tekst, 0, $dlugosc) . '...';
}

echo skroc("Bardzo długi tekst, który chcemy skrócić", 20);
// Bardzo długi tekst, ...
?>

 

substr_replace()

Funkcja substr_replace() zastępuje fragment łańcucha innym, określonym przez użytkownika. Budowa: substr_replace(string,zamiennik,początek[,długość])

jeżeli:

początek>0 – zastępowanie zacznie się od znaku o podanym numerze

początek<0 – zastępowanie zacznie się od miejsca = długość-początek

długość>0 – tyle znaków ciągu zostanie zmienione od początku

długość<0 – zastępowanie zakończy się o podaną liczbę znaków przed końcem

<?php
$var = 'ABCDEFGH:/MNRPQR/'; 
echo "Orginalny tekst: $var<hr />\n"; 

/* poniższe przykłady zamieniają całe $var na 'bob'. */ 
echo substr_replace($var, 'bob', 0) . "<br />\n"; 
echo substr_replace($var, 'bob', 0, strlen($var)) . "<br />\n"; 

/* Dołącza napis 'bob' przed zmienną $var. */ 
echo substr_replace($var, 'bob', 0, 0) . "<br />\n"; 

/* Zastępuje napis 'MNRPQR' w zmiennej $var napisem 'bob'. */ 
echo substr_replace($var, 'bob', 10, -1) . "<br />\n"; 
echo substr_replace($var, 'bob', -7, -1) . "<br />\n"; 

/* usuwa 'MNRPQR' z $var. */ 
echo substr_replace($var, '', 10, -1) . "<br />\n"; 

//operacje na tablicach
$input = array('A: XXX', 'B: XXX', 'C: XXX'); 
//zamienia XXX w każdym elemencie tablicy na YYY. 
echo implode('; ', substr_replace($input, 'YYY', 3, 3))."\n"; 

// sytuacja kiedy każdy element tablicy jest inny 
$replace = array('AAA', 'BBB', 'CCC'); 
echo implode('; ', substr_replace($input, $replace, 3, 3))."\n"; 

// sytuacja kiedy długości sa pobierane z tablicy. 
$length = array(1, 2, 3); 
echo implode('; ', substr_replace($input, $replace, 3, $length))."\n"; 
?>

 

strstr() / stristr() / strrchr()

strstr(string,find[,przed_czy_po])

Wyszukuje podane znaki w ciągu, po czym wyświetla to co jest za znakiem (trzeci argument domyślnie ustawiony jest na false). Jeżeli użyjemy trzeciego argumentu z wartością true to wyświetli ciąg znaków poprzedzający szukany element. Funkcja rozróżnia wielkość znaków.

<?php
$email = 'name@example.com';

// Zwróć fragment od znalezionego ciągu
$domain = strstr($email, '@');
echo $domain; // @example.com

// Zwróć fragment PRZED znalezionym ciągiem (PHP 5.3+)
$user = strstr($email, '@', true);
echo $user; // name

// stristr() - bez względu na wielkość liter
$tekst = "Hello World";
echo stristr($tekst, "WORLD"); // World

// strrchr() - ostatnie wystąpienie
$path = '/var/www/html/index.php';
$filename = strrchr($path, '/');
echo $filename; // /index.php
?>

strtr()

Zamienia podany ciąg znaków na inny. Budowa: strtr(string,from,to) lub strtr(string,array)

<?php
// Zamiana znaków (jeden na jeden)
echo strtr("Hilla Warld", "ia", "eo"); // Hello World

// Zamiana par (tablica asocjacyjna)
$trans = [
"hello" => "hi",
"world" => "earth"
];
echo strtr("Hello world", $trans); // Hi earth

// Praktyczny przykład - usuwanie polskich znaków
$polish = [
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l',
'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 'ż' => 'z',
'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'E', 'Ł' => 'L',
'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S', 'Ź' => 'Z', 'Ż' => 'Z'
];
$tekst = "Zażółć gęślą jaźń";
echo strtr($tekst, $polish); // Zazolc gesla jazn
?>

trim() / ltrim() / rtrim()

Funkcja trim() usuwa następujące znaki z początku i końca ciągu: odstępy, tabulacje poziome (\t) i pionowe (\x0b), nowy wiersz (\n), NULL (\0), powrót karetki (\r). Budowa:

trim(string[, lista_znaków])

parametr lista_znaków pozwala na określenie niestandardowych znaków do usunięcia.

Domyślnie usuwane znaki:

  •  – spacja
  • \t – tabulator
  • \n – nowa linia
  • \r – powrót karetki
  • \0 – NULL
  • \x0B – tabulator pionowy
<?php

$text = " \t\tTutaj jest tekst :) ... \n";

// trim() - z początku i końca
echo trim($text); // "Tutaj jest tekst :) ..."

// ltrim() - tylko z lewej (początku)
echo ltrim($text); // "Tutaj jest tekst :) ... \n"

// rtrim() - tylko z prawej (końca)
echo rtrim($text); // " \t\tTutaj jest tekst :) ..."

// Własne znaki do usunięcia
echo trim($text, " \t."); // "Tutaj jest tekst :) ...\n"

// Praktyczny przykład - czyszczenie inputu
$input = trim($_POST['username'] ?? '');

?>

Przykład:

Funkcja PHP tworząca przyjazne linki

function slugify(string $text): string {
// Transliteracja Unicode na ASCII
$text = transliterator_transliterate('Any-Latin; Latin-ASCII', $text);

$text = strtolower($text);
$text = preg_replace('/[^a-z0-9]+/', '-', $text);
$text = trim($text, '-');

return $text;
}

Przykład:

Kwota, liczba słownie PHP

<?php

if (!function_exists('str_split')) {
function str_split($string, $len = 1) {
if ($len < 1) return false;

$result = [];
for ($i = 0; $i < ceil(strlen($string) / $len); $i++) {
$result[$i] = substr($string, $len * $i, $len);
}
return $result;
}
}

$slowa = [
'minus',
['zero','jeden','dwa','trzy','cztery','pięć','sześć','siedem','osiem','dziewięć'], ['dziesięć','jedenaście','dwanaście','trzynaście','czternaście','piętnaście','szesnaście','siedemnaście','osiemnaście','dziewiętnaście'], ['dziesięć','dwadzieścia','trzydzieści','czterdzieści','pięćdziesiąt','sześćdziesiąt','siedemdziesiąt','osiemdziesiąt','dziewięćdziesiąt'],
['sto','dwieście','trzysta','czterysta','pięćset','sześćset','siedemset','osiemset','dziewięćset'],
['tysiąc','tysiące','tysięcy'],
['milion','miliony','milionów'],
['miliard','miliardy','miliardów'],
['bilion','biliony','bilionów'],
['biliard','biliardy','biliardów'],
['trylion','tryliony','trylionów'],
['tryliard','tryliardy','tryliardów'],
['kwadrylion','kwadryliony','kwadrylionów'],
['kwintylion','kwintyliony','kwintylionów'],
['sekstylion','sekstyliony','sekstylionów'],
['septylion','septyliony','septylionów'],
['oktylion','oktyliony','oktylionów'],
['nonylion','nonyliony','nonylionów'],
['decylion','decyliony','decylionów']
];

function odmiana($odmiany, $int) {
$txt = $odmiany[2];

if ($int == 1) {
$txt = $odmiany[0];
}

$jednosci = (int) substr($int, -1);
$reszta = $int % 100;

if (($jednosci > 1 && $jednosci < 5) && !($reszta > 10 && $reszta < 20)) {
$txt = $odmiany[1];
}

return $txt;
}

function liczba($int) {
global $slowa;

$wynik = '';
$j = abs((int) $int);

if ($j == 0) {
return $slowa[1][0];
}

$jednosci = $j % 10;
$dziesiatki = ($j % 100 - $jednosci) / 10;
$setki = ($j - $dziesiatki * 10 - $jednosci) / 100;

if ($setki > 0) {
$wynik .= $slowa[4][$setki - 1] . ' ';
}

if ($dziesiatki > 0) {
if ($dziesiatki == 1) {
$wynik .= $slowa[2][$jednosci] . ' ';
} else {
$wynik .= $slowa[3][$dziesiatki - 1] . ' ';
}
}

if ($jednosci > 0 && $dziesiatki != 1) {
$wynik .= $slowa[1][$jednosci] . ' ';
}

return $wynik;
}

function slownie($int) {
global $slowa;

$in = preg_replace('/[^-\d]+/', '', $int);
$out = '';

if ($in[0] == '-') {
$in = substr($in, 1);
$out = $slowa[0] . ' ';
}

$txt = str_split(strrev($in), 3);

if ($in == 0) {
$out = $slowa[1][0] . ' ';
}

for ($i = count($txt) - 1; $i >= 0; $i--) {
$liczba = (int) strrev($txt[$i]);

if ($liczba > 0) {
if ($i == 0) {
$out .= liczba($liczba) . ' ';
} else {
$out .= ($liczba > 1 ? liczba($liczba) . ' ' : '')
. odmiana($slowa[4 + $i], $liczba) . ' ';
}
}
}

return trim($out);
}

function kwotaslownie($kwota) {
$kwota = explode('.', $kwota);

$zl = preg_replace('/[^-\d]+/', '', $kwota[0]);
$gr = preg_replace('/[^\d]+/', '', substr($kwota[1] ?? '0', 0, 2));

while (strlen($gr) < 2) {
$gr .= '0';
}

echo slownie($zl) . ' ' . odmiana(['złoty', 'złote', 'złotych'], $zl)
. ((int)$gr == 0 ? '' :
' ' . slownie($gr) . ' ' . odmiana(['grosz', 'grosze', 'groszy'], $gr));
}
?>

ZADANIA:

Zadanie 1

Napisz funkcję zmieniającą wielkość liter w tekście na:

  • wielkie litery
  • małe litery
  • każdy wyraz pisany wielką literą
  • jak w zdaniu

Zadanie 2

Napisz funkcję sprawdzającą domenę podanego adresu e-mail.

Zadanie 3

Stwórz funkcję zamieniającą podany wyraz w tekście na inny.

Zadanie 4

Napisz skrypt szyfrujący hasło. Użyj password_hash() i password_verify().

Zadanie 5

W zdaniu „Jestem mistrzem” dokonaj zmian przy pomocy substr i substr_replace:

  1. Zamień „Jestem” na swoje imię
  2. Dodaj na końcu „PHP”
  3. Wyświetl tylko „mistrzem”

 

 

Wyrażenia regularne i funkcja preg_match()

Wyrażenie regularne (regex, regexp) to wzorzec służący do wyszukiwania i manipulowania tekstem.

preg_match()

funkcja, która sprawdza występowanie podanego wzorca w danym ciągu. Jest przydatna w przypadku walidacji formularzy np. gdy chcemy sprawdzić czy została podana cyfra i znak specjalny w haśle, albo czy mail został podany poprawnie. Jeżeli wyrażenie zostanie znalezione w ciągu, funkcja zwraca 1, w przeciwnym wypadku zwraca 0.

preg_match(string $pattern, string $subject [, array &$matches])

Zwraca:

  • 1 – dopasowano
  • 0 – brak dopasowania
  • false – błąd

Znaki specjalne i metacharaktery

 

Znak Znaczenie Przykład
. Dowolny znak /k.t/ → kot, kit, k1t
^ Początek stringa /^Kot/ → Kot na początku
$ Koniec stringa /kot$/ → kot na końcu
\ Znak ucieczki /\./ → dosłowna kropka
` ` OR (lub)
() Grupowanie `/(kot
[] Zbiór znaków /[aeiou]/ → samogłoska
[^] Negacja zbioru /[^0-9]/ → NIE cyfra

Ale jak się tworzy wyrażenia regularne…

/…/

Wyrażenie, którego szukamy musi znajdować się pomiędzy dwoma slash’ami.

preg_match('/kot/', 'Ala ma kota.');

wyrażenie /kot/ będzie pasowało do każdego ciągu, w którym znajda sie obok siebie litery k o t. Nie ma znaczenia, czy są wewnątrz ciągu, na początku, czy na końcu. Znaczenie ma wielkość liter!

Przyklad:

preg_match('/kot/', 'Ala ma kota.');                  
preg_match('/kot/', 'Ala ma kota na punkcie kotów.');
preg_match('/kot/', 'Kotka i małe koty.');

^ i $

Jeżeli chcemy określić, czy dane wyrażenie ma znajdować się na początku ciągu używamy ^, jeżeli ma byc na końcy – używamy $.

Przyklad:

preg_match('/^Kot/', 'Kot należy do Ali.');           
preg_match('/^kot/', 'Ten kot jest czarny.');
preg_match('/kot$/', 'Oto kot');
preg_match('/kot$/', 'Oto kot.');
preg_match('/^kot$/', 'kot');

. (kropka)

Kropka służy do znalezienia dowolnego znaku w ciągu. Jedna kropka to jeden znak.

Przyklad:

preg_match('/k.t/', 'Ala ma kota.');                
preg_match('/k.t/', 'Ten kotek ma czarną kitę.');
preg_match('/k.t/', 'Wypłynął kutrem.');
preg_match('/k..t/', 'karta');

Kropka jest znakiem specjalnym, więc jeżeli chcielibyśmy sprawdzić czy w ciągu występuje kropka to musimy poprzedzić ją \ (backslashem)

Przyklad:

preg_match('/\./', 'Ala ma kota.');                

Znaki specjalne

Poza kropką istnieją inne znaki specjalne, które należy poprzedzić znakiem \ (backslash).

. \ / * + ? [ ^ $ | { ( )

Przyklad:

preg_match('/\?/', 'Ala ma kota?');                

[…]

W nawiasach kwadratowych określamy zbiory znaków jakie chcemy wyszukać. Musi wystąpić dokładnie jeden znak, obojętnie który oraz obojętnie w jakiej kolejności.

Przyklad:

preg_match('/[jest]/', 'se');                 
preg_match('/[Ala]/', 'Ala ma kota');       

Możemy zbiory znaków łączyć swobodnie ze zwykłymi znakami:

Przyklad:

preg_match('/ol[wer]/', 'wyrewolwerowane pole');       

Zbiory znaków mają swoje zakresy:

  • [a-z] – oznacza dowolną małą literę od a do z
  • [^a-z] – negacja [a-z], nie może wystąpić żadna mała litera od a do z
  • [A-Z] – oznacza dowolną wielką literę od A do Z
  • [^A-Z] – negacja [A-Z], nie może wystąpić żadna duża litera od A do Z
  • [0-9] lub \d– oznacza dowolną cyfrę
  • [^0-9] lub \D– negacja [0-9], nie może wystąpić żadna cyfra
  • \w – oznacza wszystkie litery, cyfry i podkreślnik – [a-zA-Z0-9_]
  • \W – stanowi negację \w – [^a-zA-Z0-9_]
  • \s – oznacza znaki specjalne – spację, \n, \r\ i \t
  • \S – stanowi negację \s

zakresy możemy łączyć ze sobą

Przyklad:

Jedna z liter od a do z mała lub duża lub dowolna cyfra
preg_match('/[a-zA-Z0-9]/', 'Jakiś ciąg znaków: liter aąbcć, cyfr 123456 oraz znaków !@#$%');       

aby zanegowac dany zbiór umieszczamy na jego początku ^

Przyklad:

Nie może wystąpić żadna mała litera
preg_match('/abc[^a-z]/', 'abcdef');  //nie będzie dopasowania, bo po abc nie może wystąpić żadna mała litera

preg_match('/abc[^a-z]/', 'abc123');  // poprawne dopasowanie, po abc nie wystepuje żadna mała litera

|, czyli OR

jeżeli chcemy sprawdzić, czy któryś z ciągów istnieje, używamy |. Oznacza on to samo co logiczne OR. Sprawdzana jest całość członu po lewej lub prawej stronie |.

Przyklad:

preg_match('/abc|123/', 'abc');  
preg_match('/abc|123/', '123');  

(), czyli grupowanie

działa tak jak w matematyce, gdy musimy pogrupować elementy do dopasowania. Wyrażenie wewnątrz nawiasów traktujemy jako całość.

Przyklad:

preg_match('/^Oh(Karol|Karolina)$)/', 'OhKarol');  

*, +, ?, {}, czyli kwantyfikatory

jeżeli chcemy określić ile dopasowań w ciągu ma wystąpić, to używamy kwantyfikatorów.

  • * (gwiazdka) oznacza zero lub więcej wystąpień
  • + (plus) oznacza jedno lub więcej wystąpień
  • ? (znak zapytania) oznacza zero lub jedno wystąpienie
  • {n} oznacza dokładnie n wystąpień znaku lub zakresu przed nawiasem
  • {n,} oznacza co najmniej n wystąpień znaku lub zakresu przed nawiasem
  • {m, n} oznacza od m do n wystąpień znaku lub zakresu przed nawiasem

Przyklady:

//wyrażenie pasuje do wszystkich ciągów

//* zero lub więcej wystąpień
preg_match('/abc[A-Z]*/', 'abc');  
preg_match('/abc[A-Z]*/', 'abcC'); 
preg_match('/abc[A-Z]*/', 'abcXY'); 
preg_match('/abc[A-Z]*/', 'abcABCDE'); 

//+ jedno lub więcej wystąpień
preg_match('/abc[A-Z]+/', 'abcB'); 
preg_match('/abc[A-Z]+/', 'abcC'); 
preg_match('/abc[A-Z]+/', 'abcXY'); 
preg_match('/abc[A-Z]+/', 'abcABCDE'); 

//? zero lub jedno wystąpienie
preg_match('/abc[A-Z]?/', 'abc'); 
preg_match('/abc[A-Z]?/', 'abcC'); 
preg_match('/abc[A-Z]?/', 'abcX'); 
preg_match('/abc[A-Z]?/', 'abcZ'); 

//od 1 do 3 wystąpień znaków z zakresu [A-Z]
preg_match('/abc[A-Z]{1,3}/', 'abcA'); 
preg_match('/abc[A-Z]{1,3}/', 'abcAC'); 
preg_match('/abc[A-Z]{1,3}/', 'abcAHX'); 

//od 1 do 3 wystąpień znaku c 
preg_match('/abc{1,3}/', 'abcc'); 
preg_match('/abc{1,3}/', 'abccc'); 
preg_match('/abc{1,3}/', 'abcccc'); 
//co najmniej 3 wystąpienia znaków z zakresu [A-Z]
preg_match('/abc[A-Z]{3,}/', 'abcABC'); 
preg_match('/abc[A-Z]{3,}/', 'abcACGH'); 
preg_match('/abc[A-Z]{3,}/', 'abcAHTUX'); 
//co najmniej 3 wystąpienia znaków z zakresu (abc) 
preg_match('/(abc){3,}/', 'abcabcabc'); 

Modyfikatory

Dodawane po zamykającym /:

i – Ignoruj wielkość liter

m – Multiline (^ i $ dla każdej linii)

s – Dotall (. dopasowuje też \n)

u – UTF-8

x –Ignoruj białe znaki w wzorcu

// i - case insensitive
preg_match('/kot/i', 'KOT');     // 1 ✓

// m - multiline
$tekst = "linia1\nlinia2";
preg_match('/^linia2$/m', $tekst); // 1 ✓

// u - UTF-8
preg_match('/\w+/u', 'zażółć');  // dopasuje polskie znaki

Przechwytywanie

Wynik działania funkcji preg_match() możemy umieścić w tablicy, aby tego dokonać musimy dołożyć naszej funkcji dodatkowe parametry mianowicie: $matches i PREG_OFFSET_CAPTURE

preg_match(wyrazenie_regularne, badany_ciag, $matches, PREG_OFFSET_CAPTURE);  

Wyniki zostają wrzucone do tablicy, gdzie w indeksie zerowym znajduje się część ciągu od, której zaczęło się dopasowanie, a w indeksie od 1 do n znajdują się dopasowania.

preg_match('/(\d+)\D+(\d+)/', 'Mam 70zł, wystarczy mi to na zakup 2 książek.', $matches, PREG_OFFSET_CAPTURE);  

wykonanie tego kodu spowoduje otrzymanie tablicy:

Array
(
    [0] => Array
        (
            [0] => 70zł, wystarczy mi to na zakup 2
            [1] => 4
        )

    [1] => Array
        (
            [0] => 70
            [1] => 4
        )

    [2] => Array
        (
            [0] => 2
            [1] => 36
        )

)

pogrupowanie elementów za pomocą (), powoduje że przechwytywanie zaczyna się od nawiasów zewnętrznych

preg_match('/((\d+)\D+)(\d+)/', 'Mam 70zł, wystarczy mi to na zakup 2 książek.', $matches, PREG_OFFSET_CAPTURE); 

wykonanie tego kodu spowoduje otrzymanie tablicy:

Array
(
    [0] => Array
        (
            [0] => 70zł, wystarczy mi to na zakup 2
            [1] => 4
        )

    [1] => Array
        (
            [0] => 70zł, wystarczy mi to na zakup 
            [1] => 4
        )

    [2] => Array
        (
            [0] => 70
            [1] => 4
        )

    [3] => Array
        (
            [0] => 2
            [1] => 36
        )

)
$tekst = 'Mam 70zł, wystarczy mi to na 2 książki.';

// Proste przechwytywanie
preg_match('/(\d+)/', $tekst, $matches);
print_r($matches);
// Array([0] => 70, [1] => 70)

// Wiele grup
preg_match('/(\d+)\D+(\d+)/', $tekst, $matches);
print_r($matches);
// Array(
//   [0] => 70zł, wystarczy mi to na 2
//   [1] => 70
//   [2] => 2
// )

// PREG_OFFSET_CAPTURE - z pozycjami
preg_match('/(\d+)/', $tekst, $matches, PREG_OFFSET_CAPTURE);
print_r($matches);
// Array(
//   [0] => Array([0] => 70, [1] => 4)
//   [1] => Array([0] => 70, [1] => 4)
// )

// Nazwane grupy (PHP 7.2+)
preg_match('/(?<cyfry>\d+)/', $tekst, $matches);
echo $matches['cyfry']; // 70

Przykłady walidacji

// ----- Email:
function validateEmail(string $email): bool {
    $pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
    return preg_match($pattern, $email) === 1;
}

// Lepsze - użyj filter_var
function validateEmail(string $email): bool {
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}


// ----- Telefon:
function validatePhone(string $phone): bool {
    // Format: +48 123-456-789 lub 123456789
    $pattern = '/^(\+48\s?)?(\d{3}[\s\-]?\d{3}[\s\-]?\d{3})$/';
    return preg_match($pattern, $phone) === 1;
}


// ----- Kod pocztowy:
function validatePostcode(string $code): bool {
    // Format: 00-000
    return preg_match('/^\d{2}-\d{3}$/', $code) === 1;
}


// ----- Hasło (min 8 znaków, 1 wielka, 1 mała, 1 cyfra, 1 spec):
function validatePassword(string $password): bool {
    $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/';
    return preg_match($pattern, $password) === 1;
}


// ----- URL:
function validateURL(string $url): bool {
    $pattern = '/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/';
    return preg_match($pattern, $url) === 1;
}

// Lepsze - użyj filter_var
function validateURL(string $url): bool {
    return filter_var($url, FILTER_VALIDATE_URL) !== false;
}

Inne funkcje regex

// ----- preg_match_all() - znajdź wszystkie dopasowania:
$tekst = "Mam 3 koty, 5 psów i 2 papugi";
preg_match_all('/\d+/', $tekst, $matches);
print_r($matches[0]);
// Array([0] => 3, [1] => 5, [2] => 2)

// ----- preg_replace() - zamień dopasowane fragmenty:
$tekst = "Mam 3 koty i 5 psów";
$wynik = preg_replace('/\d+/', 'X', $tekst);
echo $wynik; // Mam X koty i X psów

// Z backreference (grupami)
$tekst = "Jan Kowalski";
$wynik = preg_replace('/(\w+) (\w+)/', '$2, $1', $tekst);
echo $wynik; // Kowalski, Jan

// ----- preg_split() - podziel string:
$tekst = "jedno,dwa;trzy:cztery";
$czesci = preg_split('/[,;:]/', $tekst);
print_r($czesci);
// Array([0] => jedno, [1] => dwa, [2] => trzy, [3] => cztery)

// ----- preg_grep() - filtruj tablicę:
$emails = ['jan@example.com', 'invalid', 'anna@test.pl'];
$valid = preg_grep('/@/', $emails);
print_r($valid);
// Array([0] => jan@example.com, [2] => anna@test.pl)

Narzędzie do testowania wyrażeń regularnych

Więcej informacji na temat wyrażeń regularnych znajdziecie na tej stronie

Zadania:

Zadanie 1. Napisz skrypt, który sprawdzi, czy podana zmienna zawiera dokładnie 1 cyfrę.

Zadanie 2. Napisz skrypt, który sprawdzi, czy nazwa pliku zawiera polskie znaki.

Zadanie 3: Napisz funkcję maskującą numer karty kredytowej (pokaż tylko ostatnie 4 cyfry).

Zadanie 4: Napisz funkcję generującą losowy, silny slug (URL-friendly ID).

Zadanie 5: Napisz funkcję wyciągającą wszystkie hashtagi z tekstu (np. z tweeta).

Tabela funkcji – szybka ściąga

Operacja Funkcja Przykład
Długość strlen(), mb_strlen() strlen("test") → 4
Pozycja strpos(), str_contains() strpos("abc", "b") → 1
Zamiana str_replace(), preg_replace() str_replace("a", "b", "cat") → „cbt”
Wycięcie substr(), mb_substr() substr("abc", 1) → „bc”
Podział explode(), preg_split() explode(",", "a,b") → [„a”,”b”]
Złączenie implode(), join() implode(",", ["a","b"]) → „a,b”
Wielkość liter strtolower(), strtoupper() strtolower("ABC") → „abc”
Czyszczenie trim(), ltrim(), rtrim() trim(" a ") → „a”
HTML htmlspecialchars(), strip_tags() htmlspecialchars("<b>") → „<b>”
Regex dopasowanie preg_match() preg_match("/\d/", "a1") → 1
Regex wszystkie preg_match_all() preg_match_all("/\d/", "a1b2") → 2

 

Dobre praktyki

1. Używaj funkcji mb_* dla tekstów wielobajtowych

// ŹLE - błędne dla UTF-8
$polish = "zażółć";
echo strlen($polish); // 10 (bajty, nie znaki!)

// DOBRZE
echo mb_strlen($polish, 'UTF-8'); // 6 (znaki)

2. Waliduj dane wejściowe

// ŹLE
$email = $_POST['email'];
// użyj bez walidacji

// DOBRZE
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
    die("Nieprawidłowy email");
}

3. Escapuj output HTML

// ŹLE - podatne na XSS
echo $_GET['name'];

// DOBRZE
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');

4. Używaj prepared statements zamiast addslashes

// ŹLE
$name = addslashes($_POST['name']);
$query = "SELECT * FROM users WHERE name='$name'";

// DOBRZE
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = :name");
$stmt->execute(['name' => $_POST['name']]);

5. Preferuj filter_var nad regex dla walidacji

// ŹLE - skomplikowane regex
preg_match('/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i', $email);

// DOBRZE - wbudowana walidacja
filter_var($email, FILTER_VALIDATE_EMAIL);

Podsumowanie

Kluczowe pojęcia:

  • String – ciąg znaków, podstawowy typ danych tekstowych
  • Regex – wzorzec do wyszukiwania i manipulacji tekstem
  • Escapowanie – zabezpieczanie znaków specjalnych
  • Multibyte – obsługa znaków wielobajtowych (UTF-8)

Pamiętaj:

  • Używaj mb_* funkcji dla tekstów z polskimi znakami
  • Zawsze escapuj output HTML (htmlspecialchars)
  • Używaj password_hash() do haseł, NIE md5() czy sha1()
  • Preferuj filter_var() nad regex dla standardowej walidacji
  • Używaj prepared statements, NIE addslashes()
  • W regex używaj modyfikatora u dla UTF-8
  • Dla regex użyj narzędzi online do testowania (regex101.com)
  • NIE ufaj danym użytkownika – zawsze waliduj i escapuj

TaskQuest — Karta pracy

KARTA PRACY

Mamy już termin zadania.

Ale użytkownik w przyszłości będzie wpisywał:

login
tytuł
opis zadania

Nie możemy zakładać, że każdy wpisany tekst jest poprawny.

TaskQuest 0.8 — porządkujemy dane tekstowe

Nie tworzymy nowej aplikacji.
Rozwijamy kod z poprzedniej lekcji.

W Lekcji 7 mieliśmy między innymi:

$tytulZadania
$terminZadania
$zadanieWykonane
$streak
$dataOstatniejAktywnosci

oraz funkcje:

komunikatTerminu()
aktualizujStreak()

Tych elementów nie usuwamy.

W tej lekcji dodamy kolejne dane.
Teraz zajmiemy się danymi, które użytkownik może wpisywać do aplikacji.
Użytkownik w przyszłości będzie podawał między innymi:

imię
login
tytuł zadania
opis zadania

Nie możemy zakładać, że każdy wpisany tekst jest poprawny.

Użytkownik może na przykład wpisać:

   Nauczyć     się PHP

albo:

Kama!!!

jako login.

Cel lekcji

Nauczyć TaskQuest:

  • porządkować tekst,
  • usuwać niepotrzebne spacje,
  • sprawdzać długość tekstu,
  • walidować login,
  • wyszukiwać fragment tekstu,
  • przygotowywać dane do późniejszego formularza.

1. Nadal bez tablic

Na potrzeby lekcji pracujemy na zmiennych:

$login = "Kama_2026";
$tytulZadania = "   Nauczyć się regexów PHP   ";
$opisZadania = "Powtórzyć wyrażenia regularne przed następną lekcją.";

Zaktualizuj $tytulZadania i dodaj nowe zmienne.
Tablice pojawią się dopiero na lekcji 9.

2. Problem z tytułem

Użytkownik może wpisać:

"   Nauczyć się regexów PHP   "

Nie chcemy zapisywać niepotrzebnych spacji na początku i końcu.

SAMODZIELNIE

Znajdź funkcję PHP, która usuwa białe znaki z początku i końca tekstu i użyj jej w kodzie.

HINT
trim()

Co jeśli użytkownik wpisze:

Nauczyć     się      PHP

Znajdź sposób na usunięcie zbędnych spacji i użyj go w kodzie.

HINT
preg_replace()

aby kilka kolejnych białych znaków zastąpić pojedynczą spacją.

POMOC
$tytulZadania = preg_replace('/\s+/', ' ', $tytulZadania);

Po wykonaniu obu operacji:

$tytulZadania = trim($tytulZadania);

$tytulZadania = preg_replace('/\s+/', ' ', $tytulZadania);

otrzymamy:

Nauczyć się regexów PHP

Zamykamy czyszczenie tytułu w funkcji

Nie chcemy za każdym razem pisać tych dwóch instrukcji.

Utwórz funkcję:

function wyczyscTytul(string $tytul): string

Funkcja powinna:

  1. usunąć spacje z początku i końca,
  2. zamienić wielokrotne białe znaki na jedną spację,
  3. zwrócić poprawiony tytuł.

POMOC

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

Następnie:

$tytulZadania = wyczyscTytul($tytulZadania);

4. Walidacja loginu

Teraz zajmiemy się loginem użytkownika.

Mamy:

$loginUzytkownika = "Kama_2026";

Ustalmy zasady TaskQuest.

Login:

  • ma od 3 do 20 znaków,
  • może zawierać litery,
  • może zawierać cyfry,
  • może zawierać _,
  • może zawierać -,
  • nie może zawierać spacji.

SAMODZIELNIE

Napisz funkcję:

function walidujLogin(string $login): bool

Funkcja ma zwrócić:

true

jeżeli login jest poprawny,

albo:

false

jeżeli login jest niepoprawny.

Wykorzystaj:

preg_match()
HINT

Początek tekstu:

^

Koniec:

$

Zakres liczby znaków:

{3,20}
POMOC

Wyrażenie regularne:

/^[a-zA-Z0-9_-]{3,20}$/

5. Przetestuj login

Sprawdź:

Ka
Kama
Kama_2026
Kama-2026
Kama 2026
Kama!!!
bardzobardzobardzobardzodlugilogin

Przed uruchomieniem programu spróbuj przewidzieć wyniki.
Pamiętaj, że funkcja:

walidujLogin()

zwraca:

true

albo:

false

6. Skracanie opisu

Na liście zadań nie chcemy wyświetlać bardzo długiego opisu.

Napisz funkcję:

function skrocOpis(string $opis, int $limit): string

Jeżeli opis jest krótszy niż limit:

return $opis;

Jeżeli jest dłuższy:

pierwsze znaki...
HINT

Możesz wykorzystać:

mb_strlen()
mb_substr()

7. Wyszukiwanie słowa w tytule

Sprawdź, czy tytuł zadania zawiera słowo:

PHP

Użyj funkcji umożliwiającej wyszukiwanie bez rozróżniania wielkości liter.

Przetestuj:

PHP
php
Php

8. ROZWIĄZANIE — w tym kodzie dodaliśmy

<?php
$login = "Kama_2026";
$tytulZadania = "   Nauczyć   się regexów   PHP   ";
$opisZadania = "Powtórzyć wyrażenia regularne przed następną lekcją.";

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

    return $tytul;
}

$tytulZadania = wyczyscTytul($tytulZadania);

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

//w sekcji wyświetlania informacji dodaliśmy
if (walidujLogin($loginUzytkownika)) {
    echo "<p>Login poprawny: $loginUzytkownika</p>";
} else {
    echo "<p>Login jest niepoprawny.</p>";
}


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

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

//w sekcji wyświetlania informacji dodaliśmy
echo "<p>"
    . skrocOpis($opisZadania, 50)
    . "</p>";

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

9. Obowiązkowy eksperyment

Usuń:

^

i:

$

z wyrażenia regularnego loginu.

Przetestuj:

!!!Kama_2026!!!

Porównaj wynik.

Odpowiedz:

Dlaczego kotwice początku i końca mają tutaj znaczenie?

Do samodzielnego sprawdzenia

  • Nadal rozwijam ten sam projekt TaskQuest.
  • Zachowałem zmienne z poprzedniej lekcji.
  • Wiem, czym różni się $imieUzytkownika od $loginUzytkownika.
  • Użyłem trim().
  • Potrafię usunąć wielokrotne spacje.
  • Użyłem preg_match().
  • Login ma ustalone zasady.
  • Potrafię skrócić opis.
  • Potrafię znaleźć fragment napisu.
  • Nadal nie używam tablic.

Pytania kontrolne

  1. Co robi trim()?
  2. Po co w regex używamy ^?
  3. Po co używamy $?
  4. Co oznacza {3,20}?
  5. Co oznacza [a-zA-Z0-9_-]?
  6. Dlaczego do polskich tekstów warto używać funkcji mb_*?
  7. Czym różni się walidacja od formatowania tekstu?
  8. Dlaczego $imieUzytkownika i $loginUzytkownika są dwiema różnymi zmiennymi?
  9. Dlaczego w Lekcji 8 nie usuwamy funkcji komunikatTerminu() i aktualizujStreak()?
Mój kod z kompletną aplikacją od lekcji 1 (możesz porównać ze swoją). Pamiętaj moje rozwiązanie nie jest obligatoryjne, tworzysz własny projekt.
<?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;
}


$tytulZadania = "   Nauczyć   się regexów   PHP   ";

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


$opisZadania = "Powtórzyć wyrażenia regularne przed następną lekcją.";
function skrocOpis(string $opis,int $limit = 50): string {
    if (mb_strlen($opis) <= $limit) {
        return $opis;
    }

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



$terminZadania = "2026-09-20";

$streak = 5;
$dataOstatniejAktywnosci = "2026-09-14";
$zadanieWykonane = true;

$status = $zadanieWykonane ? "Aktywny" : "Nieaktywny";

function komunikatTerminu(string $terminZadania): string{
    $dzisiaj = new DateTime('today');
    $termin = new DateTime($terminZadania);
    $roznica = $dzisiaj->diff($termin);
    // print_r($roznica);
    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;
}

$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;
        }
    </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." (" . round($procentPostepu, 2) . "%)"; ?> 
        </h3>

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

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

        <p>Zadanie: <strong><?= $tytulZadania; ?></strong></p>
        <p><?= skrocOpis($opisZadania, 50) ?></p>
        <p>Termin: <strong><?= $terminZadania; ?></strong></p>
        <p>Komunikat: <strong><?= komunikatTerminu($terminZadania); ?></strong></p>
        <p>Streak: <strong><?= $streak ?></strong></p>
        <p>Stan: <strong><?= $zadanieWykonane ? "Wykonane" : "W trakcie"; ?></strong></p>
        <?php
        if (stripos($tytulZadania, 'PHP') !== false) {
            echo "<p>To zadanie dotyczy PHP.</p>";
        }
        ?>
    </div>
</body>
</html>

TaskQuest — wygląd

KARTA PRACY

Podrasujemy trochę wygląd apki, żeby nie była nudna

Twoja aplikacja dostaje wygląd: czarne tło, zielone litery, deszcz znaków i łunę, która chodzi za kursorem. Od tej lekcji nie wpisujesz już stylów w pliku PHP — leżą w osobnym pliku CSS, który będzie rósł razem z aplikacją.

Potrzebne pliki dostajesz od nauczyciela:

  • taskquest.css — wygląd,
  • taskquest.js — efekty reagujące na mysz.

Krok 1 — katalogi

W katalogu projektu (tam, gdzie leży Twój plik PHP z Lekcji 8) utwórz dwa katalogi:

 
text
css/
js/

Utwórz w folderze css plik o nazwie taskquest.css i wklej do niego poniższą zawartość

/* =====================================================================
   TaskQuest — motyw MATRIX
   ---------------------------------------------------------------------
   Plik rośnie razem z aplikacją. Każda sekcja odpowiada jednej lekcji:
   dopisujesz ją wtedy, gdy w HTML-u pojawiają się jej elementy.
   ===================================================================== */


/* =====================================================================
   LEKCJA 8 — FUNDAMENT (tło, terminal, panel, nagłówki, pasek XP)
   ===================================================================== */

:root {
    /* Tło */
    --tlo:        #000300;
    --tlo-panel:  rgba(0, 12, 3, 0.72);

    /* Zieleń — jeden kolor w czterech natężeniach */
    --zielen:       #00ff41;   /* główny — tekst wyróżniony, ramki */
    --zielen-jasna: #b6ffc8;   /* najjaśniejsza — liczby, wartości */
    --zielen-ciemna:#00913a;   /* przygaszona — opisy, etykiety */
    --zielen-tlo:   rgba(0, 255, 65, 0.07);

    /* Tekst */
    --tekst:       #86f5a4;
    --tekst-slaby: #2f8c4d;

    /* Jedyny kolor spoza zieleni — wyłącznie awarie i terminy */
    --alarm: #ff4d4d;

    /* Ramki */
    --ramka:       rgba(0, 255, 65, 0.30);
    --ramka-slaba: rgba(0, 255, 65, 0.14);

    /* Czcionka — cała aplikacja jest terminalem */
    --czcionka: "Share Tech Mono", ui-monospace, "Cascadia Mono", Consolas,
                "DejaVu Sans Mono", monospace;

    /* Pozycja kursora — ustawia ją js/taskquest.js */
    --mysz-x: 50vw;
    --mysz-y: 15vh;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    background: var(--tlo);
}

body {
    margin: 0;
    padding: 36px 20px 64px;
    min-height: 100vh;
    font-family: var(--czcionka);
    font-size: 16px;
    line-height: 1.7;
    color: var(--tekst);
    background:
        radial-gradient(1000px circle at 50% -15%, rgba(0, 255, 65, 0.10), transparent 60%),
        var(--tlo);
    background-attachment: fixed;
}

/* Płótno z deszczem znaków tworzy skrypt — tu tylko je umieszczamy. */
#deszcz-matrix {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    opacity: 0.38;
}

/* Linie skanujące starego monitora. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: repeating-linear-gradient(180deg,
                rgba(0, 0, 0, 0.30) 0 1px, transparent 1px 3px);
}

/* Łuna podążająca za kursorem. Współrzędne podaje JS. */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background: radial-gradient(360px circle at var(--mysz-x) var(--mysz-y),
                rgba(0, 255, 65, 0.16), rgba(0, 255, 65, 0.05) 45%, transparent 72%);
}

/* ---- Panel: główna „płyta” aplikacji -------------------------------- */
.panel {
    position: relative;
    z-index: 3;
    overflow: hidden;
    max-width: 760px;
    margin: 0 auto 28px;
    padding: 30px 32px;
    border: 1px solid var(--ramka);
    border-radius: 6px;
    background: var(--tlo-panel);
    -webkit-backdrop-filter: blur(3px);
            backdrop-filter: blur(3px);
    box-shadow:
        0 0 60px rgba(0, 0, 0, 0.9),
        0 0 30px rgba(0, 255, 65, 0.10),
        inset 0 0 70px rgba(0, 255, 65, 0.05);
    animation: wejscie 0.5s ease-out both;
}

/* Smuga skanera przesuwająca się przez panel. */
.panel::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--zielen), transparent);
    opacity: 0.45;
    animation: skaner 7s linear infinite;
}

/* Podświetlenie wnętrza panelu w miejscu kursora. */
.panel::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(300px circle at var(--kursor-x, 50%) var(--kursor-y, 0%),
                rgba(0, 255, 65, 0.10), transparent 68%);
}

.panel > * {
    position: relative;
    z-index: 1;
}

@keyframes skaner {
    from { top: -2px; }
    to   { top: 100%; }
}

@keyframes wejscie {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
}

/* ---- Typografia ----------------------------------------------------- */
h1 {
    margin: 0 0 4px;
    font-size: clamp(1.9rem, 6vw, 2.6rem);
    font-weight: 400;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--zielen);
    text-shadow:
        0 0 8px rgba(0, 255, 65, 0.9),
        0 0 26px rgba(0, 255, 65, 0.45);
    animation: migotanie 6s steps(1) infinite;
}

/* Delikatne migotanie kineskopu — nie częściej niż raz na kilka sekund. */
@keyframes migotanie {
    0%, 92%, 96%, 100% { opacity: 1; }
    94%                { opacity: 0.72; }
}

h2 {
    margin: 32px 0 10px;
    font-size: 1.15rem;
    font-weight: 400;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--zielen);
}

/* Nagłówki sekcji wyglądają jak wpis w konsoli. */
h2::before {
    content: "> ";
    color: var(--zielen-ciemna);
}

h3 {
    margin: 20px 0 8px;
    font-size: 1rem;
    font-weight: 400;
    color: var(--zielen-jasna);
}

p {
    margin: 8px 0;
    max-width: 72ch;
}

strong {
    color: var(--zielen-jasna);
    font-weight: 400;
    text-shadow: 0 0 10px rgba(0, 255, 65, 0.5);
}

a {
    color: var(--zielen);
    text-decoration: none;
    border-bottom: 1px solid var(--ramka);
    transition: color 0.15s, text-shadow 0.15s, background 0.15s;
}

a:hover {
    background: var(--zielen-tlo);
    text-shadow: 0 0 12px rgba(0, 255, 65, 0.9);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--zielen);
    outline-offset: 3px;
}

code, pre {
    color: var(--zielen-jasna);
}

hr {
    border: 0;
    height: 1px;
    margin: 24px 0;
    background: linear-gradient(90deg, transparent, var(--ramka), transparent);
}

/* ---- Pasek XP -------------------------------------------------------
   Działa w obu wersjach z kursu: znakowej (█ ░) i segmentowej (<span>). */
.pasek-xp {
    display: flex;
    gap: 5px;
    margin: 14px 0 18px;
    font-size: 1.5rem;
    line-height: 1;
    letter-spacing: 0.1em;
    color: var(--zielen);
    text-shadow: 0 0 12px rgba(0, 255, 65, 0.8);
}

.segment {
    width: 36px;
    height: 16px;
    border: 1px solid var(--ramka-slaba);
    background: rgba(0, 255, 65, 0.04);
}

.segment.aktywny {
    border-color: transparent;
    background: var(--zielen);
    box-shadow: 0 0 14px rgba(0, 255, 65, 0.8);
    animation: puls 2.6s ease-in-out infinite;
}

@keyframes puls {
    50% { box-shadow: 0 0 24px rgba(0, 255, 65, 1); }
}

/* ---- Komunikat o awansie -------------------------------------------- */
.awans {
    display: inline-block;
    margin: 10px 0;
    padding: 6px 14px;
    border: 1px solid var(--ramka);
    background: var(--zielen-tlo);
    color: var(--zielen);
    text-shadow: 0 0 12px rgba(0, 255, 65, 0.7);
}

/* ---- Wersja aplikacji w stopce (klasa dochodzi w Lekcji 10) --------- */
.wersja {
    margin-top: 26px;
    padding-top: 14px;
    border-top: 1px solid var(--ramka-slaba);
    font-size: 0.8rem;
    letter-spacing: 0.2em;
    color: var(--tekst-slaby);
}

/* ---- Telefon --------------------------------------------------------- */
@media (max-width: 620px) {
    body { padding: 16px 12px 40px; }
    .panel { padding: 20px 16px; }
    .pasek-xp { font-size: 1.1rem; }
    .segment { width: 24px; }
}

/* ---- Szacunek dla ustawień systemowych ------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation: none !important;
        transition: none !important;
    }
}


/* =====================================================================
   LEKCJA 9 — LISTA ZADAŃ
   ===================================================================== */

.zadanie {
    position: relative;
    margin: 14px 0;
    padding: 6px 18px;
    border: 1px solid var(--ramka-slaba);
    border-left: 2px solid var(--zielen);
    background: rgba(0, 255, 65, 0.03);
    transform: perspective(900px)
               rotateX(var(--obrot-x, 0deg))
               rotateY(var(--obrot-y, 0deg));
    transition: transform 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}

.zadanie::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(200px circle at var(--kursor-x, 50%) var(--kursor-y, 50%),
                rgba(0, 255, 65, 0.13), transparent 70%);
    opacity: 0;
    transition: opacity 0.25s;
}

.zadanie:hover {
    background: rgba(0, 255, 65, 0.06);
    box-shadow: 0 0 26px rgba(0, 255, 65, 0.22);
}

.zadanie:hover::after { opacity: 1; }

.zadanie p { margin: 6px 0; }

.zadanie.wykonane {
    border-left-color: var(--zielen-ciemna);
    opacity: 0.5;
}

.zadanie.wykonane strong {
    text-decoration: line-through;
    color: var(--tekst-slaby);
    text-shadow: none;
}


/* =====================================================================
   LEKCJA 12 — RAMKA AWARII
   ===================================================================== */

.blad {
    margin: 14px 0;
    padding: 10px 16px;
    border: 1px solid rgba(255, 77, 77, 0.45);
    border-left: 2px solid var(--alarm);
    background: rgba(255, 77, 77, 0.07);
    color: #ffb3b3;
}

.blad::before {
    content: "[!] ";
    color: var(--alarm);
}

.blad strong { color: var(--alarm); text-shadow: none; }


/* =====================================================================
   LEKCJA 13 — FORMULARZE I KOMUNIKAT O SUKCESIE
   ===================================================================== */

.sukces {
    margin: 14px 0;
    padding: 10px 16px;
    border: 1px solid var(--ramka);
    border-left: 2px solid var(--zielen);
    background: var(--zielen-tlo);
    color: var(--zielen-jasna);
}

.sukces::before {
    content: "[OK] ";
    color: var(--zielen);
}

form label {
    display: block;
    margin-top: 16px;
    font-size: 0.8rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--zielen-ciemna);
}

form input,
form textarea,
form select {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--ramka-slaba);
    background: rgba(0, 0, 0, 0.6);
    color: var(--zielen-jasna);
    font: inherit;
    transition: border-color 0.15s, box-shadow 0.15s;
}

form input:focus,
form textarea:focus,
form select:focus {
    border-color: var(--zielen);
    box-shadow: 0 0 0 1px var(--zielen), 0 0 20px rgba(0, 255, 65, 0.3);
    outline: none;
}

form input::placeholder,
form textarea::placeholder { color: var(--tekst-slaby); }

form button,
button {
    margin-top: 18px;
    padding: 10px 20px;
    border: 1px solid var(--zielen);
    background: transparent;
    color: var(--zielen);
    font: inherit;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}

form button:hover,
button:hover {
    background: var(--zielen);
    color: #000300;
    box-shadow: 0 0 24px rgba(0, 255, 65, 0.6);
}


/* =====================================================================
   LEKCJA 15 — ZADANIE PO TERMINIE
   ===================================================================== */

.zadanie.po-terminie {
    border-color: rgba(255, 77, 77, 0.30);
    border-left-color: var(--alarm);
    background: rgba(255, 77, 77, 0.05);
}

.zadanie.po-terminie:hover {
    background: rgba(255, 77, 77, 0.08);
    box-shadow: 0 0 26px rgba(255, 77, 77, 0.25);
}


/* =====================================================================
   LEKCJA 18 — RANKING
   ===================================================================== */

ol {
    list-style: none;
    counter-reset: miejsce;
    margin: 12px 0;
    padding: 0;
}

ol li {
    counter-increment: miejsce;
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 6px 0;
    padding: 8px 14px;
    border: 1px solid var(--ramka-slaba);
    background: rgba(0, 255, 65, 0.03);
}

ol li::before {
    content: "[" counter(miejsce) "]";
    flex: 0 0 auto;
    color: var(--zielen-ciemna);
}

ol li:first-child {
    border-color: var(--ramka);
    box-shadow: inset 0 0 30px rgba(0, 255, 65, 0.08);
}

ol li:first-child::before {
    color: var(--zielen);
    text-shadow: 0 0 12px rgba(0, 255, 65, 0.9);
}


/* =====================================================================
   LEKCJA 19 — PRZYCISKI PRZY ZADANIACH
   ===================================================================== */

form.akcja {
    display: inline-block;
    margin: 0 8px 10px 0;
}

form.akcja button {
    margin-top: 0;
    padding: 5px 12px;
    font-size: 0.78rem;
}

form.akcja.usun button {
    border-color: rgba(255, 77, 77, 0.6);
    color: var(--alarm);
}

form.akcja.usun button:hover {
    background: var(--alarm);
    color: #1a0000;
    box-shadow: 0 0 22px rgba(255, 77, 77, 0.5);
}


/* =====================================================================
   LEKCJA 21 — AVATAR
   ===================================================================== */

.avatar {
    display: inline-block;
    width: 72px;
    height: 72px;
    border: 1px solid var(--zielen);
    border-radius: 50%;
    object-fit: cover;
    vertical-align: middle;
    box-shadow: 0 0 22px rgba(0, 255, 65, 0.45);
    filter: saturate(0.4) sepia(1) hue-rotate(60deg) contrast(1.15);
}

.avatar-litera {
    background: var(--zielen-tlo);
    color: var(--zielen);
    font-size: 2rem;
    line-height: 70px;
    text-align: center;
    text-shadow: 0 0 14px rgba(0, 255, 65, 0.9);
    filter: none;
}

form input[type="file"] {
    padding: 14px;
    border-style: dashed;
    cursor: pointer;
}


/* =====================================================================
   LEKCJA 22 — LOGOWANIE I REJESTRACJA
   ===================================================================== */

.panel.logowanie {
    max-width: 420px;
    margin-top: 8vh;
}

.panel.logowanie h1 {
    text-align: center;
    font-size: 1.9rem;
}

Utwórz w folderze js plik o nazwie taskquest.js i wklej do niego poniższą zawartość

/* =====================================================================
   TaskQuest — warstwa efektów motywu MATRIX.
   Skrypt nie zmienia HTML-a aplikacji i nie jest potrzebny do jej
   działania. Robi trzy rzeczy:
     1. łuna podążająca za kursorem,
     2. podświetlenie i pochylenie elementu pod kursorem,
     3. deszcz znaków w tle.
   ===================================================================== */

(function () {
    "use strict";

    var korzen = document.documentElement;
    var bezAnimacji = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    /* --- 1. Łuna podąża za kursorem ---------------------------------- */
    /* Pozycja docelowa (cel*) i aktualna (teraz*) są osobne — dzięki temu
       światło „dogania” kursor zamiast skakać razem z nim. */
    var celX = window.innerWidth / 2;
    var celY = window.innerHeight * 0.15;
    var terazX = celX;
    var terazY = celY;
    var klatka = null;

    function rysujLune() {
        terazX += (celX - terazX) * 0.12;
        terazY += (celY - terazY) * 0.12;

        korzen.style.setProperty("--mysz-x", terazX.toFixed(1) + "px");
        korzen.style.setProperty("--mysz-y", terazY.toFixed(1) + "px");

        if (Math.abs(celX - terazX) > 0.5 || Math.abs(celY - terazY) > 0.5) {
            klatka = window.requestAnimationFrame(rysujLune);
        } else {
            klatka = null;
        }
    }

    document.addEventListener("pointermove", function (zdarzenie) {
        celX = zdarzenie.clientX;
        celY = zdarzenie.clientY;
        if (klatka === null) {
            klatka = window.requestAnimationFrame(rysujLune);
        }
    }, { passive: true });

    /* --- 2. Element pod kursorem ------------------------------------- */
    document.querySelectorAll(".panel, .zadanie").forEach(function (karta) {
        karta.addEventListener("pointermove", function (zdarzenie) {
            var obszar = karta.getBoundingClientRect();
            var x = (zdarzenie.clientX - obszar.left) / obszar.width;   /* 0..1 */
            var y = (zdarzenie.clientY - obszar.top) / obszar.height;   /* 0..1 */

            karta.style.setProperty("--kursor-x", (x * 100).toFixed(1) + "%");
            karta.style.setProperty("--kursor-y", (y * 100).toFixed(1) + "%");

            if (!bezAnimacji && karta.classList.contains("zadanie")) {
                karta.style.setProperty("--obrot-y", ((x - 0.5) * 6).toFixed(2) + "deg");
                karta.style.setProperty("--obrot-x", ((0.5 - y) * 6).toFixed(2) + "deg");
            }
        }, { passive: true });

        karta.addEventListener("pointerleave", function () {
            karta.style.setProperty("--obrot-x", "0deg");
            karta.style.setProperty("--obrot-y", "0deg");
        });
    });

    /* --- 3. Deszcz znaków -------------------------------------------- */
    /* Przy wyłączonych animacjach w systemie deszcz w ogóle nie powstaje. */
    if (bezAnimacji) {
        return;
    }

    var plotno = document.createElement("canvas");
    plotno.id = "deszcz-matrix";
    document.body.appendChild(plotno);

    var pedzel = plotno.getContext("2d");
    var ZNAKI = "アイウエオカキクケコサシスセソタチツテトナニヌネノ0123456789:.=*+-<>";
    var WIELKOSC = 16;
    var kolumny = [];

    function dopasuj() {
        plotno.width = window.innerWidth;
        plotno.height = window.innerHeight;

        var ile = Math.ceil(plotno.width / WIELKOSC);
        kolumny = [];
        for (var i = 0; i < ile; i++) {
            /* Losowy start sprawia, że kolumny nie spadają równo. */
            kolumny.push(Math.random() * -plotno.height);
        }
    }

    function rysujDeszcz() {
        /* Półprzezroczyste czarne tło zostawia po znakach gasnący ślad. */
        pedzel.fillStyle = "rgba(0, 3, 0, 0.09)";
        pedzel.fillRect(0, 0, plotno.width, plotno.height);
        pedzel.font = WIELKOSC + "px monospace";

        for (var i = 0; i < kolumny.length; i++) {
            var znak = ZNAKI.charAt(Math.floor(Math.random() * ZNAKI.length));
            var x = i * WIELKOSC;
            var y = kolumny[i];

            pedzel.fillStyle = "#00ff41";
            pedzel.fillText(znak, x, y);

            kolumny[i] = y > plotno.height && Math.random() > 0.975
                ? 0
                : y + WIELKOSC;
        }
    }

    window.addEventListener("resize", dopasuj);
    dopasuj();
    window.setInterval(rysujDeszcz, 60);
}());
 
text
css/taskquest.css
js/taskquest.js

Efekt: nic jeszcze nie widać. Przeglądarka nie wie, że te pliki istnieją.


Krok 2 — usuń stare style

W swoim pliku PHP znajdź w sekcji <head> cały blok:

 
html
<style>
    body { ... }
    .panel { ... }
    ...
</style>

Usuń go w całości, razem ze znacznikami <style> i </style>.

Efekt: strona wygląda jak surowy HTML — czarny tekst na białym tle. Tak ma być.


Krok 3 — podłącz nowe pliki

W tym samym miejscu, w którym był <style>, wpisz dwie linie:

 
html
<link rel="stylesheet" href="css/taskquest.css">
<script src="js/taskquest.js" defer></script>

Efekt: po odświeżeniu strona jest czarna, napis TaskQuest świeci na zielono, a w tle padają znaki. Rusz myszą — za kursorem idzie zielona łuna.

Zapamiętaj: ścieżkę css/taskquest.css czyta przeglądarka, nie PHP. Liczy ją od adresu strony, którą właśnie oglądasz.


Krok 4 — pasek XP

Masz teraz dwie wersje paska XP: znakową (█ i ░) oraz segmentową, która w Lekcji 8 była zakomentowana. W nowym wyglądzie segmenty świecą i pulsują, więc przełącz się na nie.

W pętli rysującej pasek zamień wypisywanie znaków na:

 
php
if ($segment <= $liczbaAktywnychSegmentow) {
    echo "<span class=\"segment aktywny\"></span>";
} else {
    echo "<span class=\"segment\"></span>";
}

Efekt: pasek XP to rząd zielonych prostokątów. Zdobyte XP pulsują.


Sprawdzenie

Odśwież stronę i sprawdź wszystkie punkty:

  • tło jest czarne, tekst zielony,
  • w tle padają znaki,
  • łuna podąża za kursorem,
  • po najechaniu myszą na panel wnętrze panelu się rozjaśnia,
  • przez panel co kilka sekund przechodzi jasna pozioma smuga,
  • pasek XP to świecące prostokąty, a nie znaki █░.

Typowe błędy

Objaw Przyczyna
Strona biała, czarny tekst zły adres w href — sprawdź, czy katalog nazywa się dokładnie css i plik taskquest.css
Wygląd działa, ale nic nie reaguje na mysz nie podłączony taskquest.js albo literówka w src
Pasek XP to nadal █░ zmiana w pętli nie została zapisana albo edytujesz inny plik
W konsoli przeglądarki (F12) czerwony komunikat 404 pliku nie ma pod podaną ścieżką

Dla chętnych

Otwórz css/taskquest.css. Na początku pliku jest blok :root ze zmiennymi. Zmień wartość --zielen na #ff9000 i odśwież stronę. Ile elementów zmieniło kolor? Wróć potem do #00ff41.

Pytanie na koniec: dlaczego zmiana jednej linii zmieniła kolor w kilkunastu miejscach?


PODSUMOWANIE LEKCJI

W skrócie

  • "..." wstawia wartości zmiennych i interpretuje \n, '...' wyświetla tekst dosłownie. Teksty łączymy kropką ..
  • Funkcje tekstowe nie zmieniają oryginalnej zmiennej – zwracają nowy tekst. Wynik trzeba przypisać: $tytul = trim($tytul);.
  • Znaki w tekście są numerowane od 0. Ujemna pozycja w substr() liczy od końca.
  • strpos() / stripos() zwracają pozycję albo false. Pozycja 0 też jest wynikiem – dlatego sprawdzamy !== false.
  • strlen() liczy bajty, nie znaki. Polska litera w UTF-8 zajmuje 2 bajty. Dla polskich tekstów używamy mb_strlen(), mb_substr(), mb_strtolower(), mb_strtoupper().
  • Wyrażenie regularne zapisujemy między /.../. Bez ^ i $ wystarczy, że wzorzec pasuje do fragmentu tekstu – przy walidacji prawie zawsze potrzebujemy obu kotwic.
  • [...] to jeden znak ze zbioru. Kwantyfikator (+, *, ?, {n,m}) dotyczy elementu stojącego bezpośrednio przed nim.
  • [a-z] nie obejmuje polskich liter (ą, ł, ż…).
  • preg_match() zwraca 1, 0 albo false (błąd we wzorcu), a nie true/false. Funkcja walidująca zwraca więc preg_match(...) === 1.
  • Formatowanie (trim(), preg_replace()) zmienia tekst. Walidacja (preg_match()) tylko sprawdza, czy tekst spełnia zasady – niczego nie poprawia.
  • Tekst wpisany przez użytkownika wyświetlamy przez htmlspecialchars(). strip_tags() nie jest zabezpieczeniem przed XSS.
  • Hasła: password_hash() przy zapisie, password_verify() przy sprawdzaniu. Nie porównujemy hashy operatorem == – ten sam tekst daje za każdym razem inny hash.

Co trzeba umieć na INF.03?

  • napisać echo, które buduje poprawny znacznik HTML z wartościami zmiennych, np.: echo "<a href='index.php?id=$id'>$nazwa</a>"; echo "<option value='$id'>$nick</option>";
  • dobrać cudzysłowy tak, aby nie zakończyć tekstu w środku znacznika (zewnętrzne ", wewnętrzne ' albo łączenie kropką),
  • przewidzieć, co wyświetli tekst w "...", a co w '...',
  • użyć podstawowych funkcji tekstowych do przygotowania danych do wyświetlenia (np. trim(), zmiana wielkości liter, skracanie).

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

  1. "Witaj $imie" → wartość zmiennej, 'Witaj $imie' → dosłownie $imie.
  2. Wynik funkcji przypisz: $tekst = trim($tekst);.
  3. strpos(...) !== false – nigdy samo if (strpos(...)).
  4. Polskie znaki → funkcje mb_*.
  5. substr($tekst, start, dlugosc) – liczymy od 0, ujemny start liczy od końca.
  6. Walidacja: preg_match('/^...$/', $tekst) === 1.
  7. \d cyfra, \s biały znak, [abc] jeden znak ze zbioru, {3,20} od 3 do 20 powtórzeń, + jeden lub więcej.
  8. Czyszczenie tytułu w TaskQuest: trim(preg_replace('/\s+/', ' ', $tytul)).
  9. Dane od użytkownika na stronę → htmlspecialchars(). Hasło → password_hash() / password_verify().

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

Funkcje – co zwracają i na co uważać

FunkcjaZwracaUwaga
strlen($t)liczbę bajtówstrlen("żółw") → 7
mb_strlen($t)liczbę znakówmb_strlen("żółw") → 4
substr($t, $start, $dl) / mb_substr()fragment tekstubrak $dl = do końca
strpos($t, $szukane)pozycję lub falsewielkość liter ma znaczenie
stripos($t, $szukane)pozycję lub falsebez rozróżniania wielkości liter
str_contains($t, $szukane)true / falserozróżnia wielkość liter
str_replace($co, $na, $t)nowy tekstzamienia wszystkie wystąpienia
strtolower() / strtoupper()nowy tekstnie zmienia polskich liter: strtoupper("zażółć") → ZAżółć
mb_strtolower() / mb_strtoupper()nowy tekstpoprawne dla polskich liter
ucfirst() / ucwords()nowy tekstzmienia tylko pierwszą literę (tekstu / słów)
trim() / ltrim() / rtrim()nowy tekstdrugi argument = własne znaki do usunięcia
explode($sep, $t)tablicętablice – lekcja 9
implode($sep, $tablica)tekstodwrotność explode()
htmlspecialchars($t)tekst z encjami< → &lt;, > → &gt;
password_hash($h, PASSWORD_DEFAULT)hashza każdym razem inny
password_verify($h, $hash)true / falsejedyny sposób sprawdzenia hasła
preg_match($wzorzec, $t)1 / 0 / falseszuka jednego dopasowania
preg_replace($wzorzec, $na, $t)nowy tekstzamienia wszystkie dopasowania

Wyrażenia regularne

ElementZnaczenie
^ / $początek / koniec tekstu
.dowolny jeden znak
\.dosłowna kropka
[abc] / [^abc]jeden znak ze zbioru / spoza zbioru
[a-z], [A-Z], [0-9]zakresy znaków
\d / \w / \scyfra / litera, cyfra lub _ / biały znak
* / + / ?0 lub więcej / 1 lub więcej / 0 lub 1
{n} / {n,} / {n,m}dokładnie n / co najmniej n / od n do m
a\|ba lub b
( )grupa
/.../i / /.../ubez rozróżniania wielkości liter / obsługa UTF-8

Schematy z TaskQuest

// czyszczenie
$t = trim(preg_replace('/\s+/', ' ', $t));

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

// skracanie
if (mb_strlen($opis) <= $limit) return $opis;
return mb_substr($opis, 0, $limit) . "...";

// wyszukiwanie bez wielkości liter
if (stripos($tytul, 'PHP') !== false) { ... }

Quiz – sprawdź się

1. Co wyświetli kod?

$imie = "Ola";
echo 'Cześć $imie' . " i $imie";

2. Co wyświetli kod?

echo strlen("kot") . " " . strlen("żółw") . " " . mb_strlen("żółw");

3. Znajdź błąd. Co wyświetli kod i jak go poprawić?

$tytul = "   PHP   ";
trim($tytul);
echo "[$tytul]";

4. Dlaczego ten kod nic nie wyświetla, chociaż tekst zawiera „PHP”? Zaproponuj dwie poprawki.

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

5. Co wyświetli każda linia?

echo substr("TaskQuest", 4);
echo substr("TaskQuest", 0, 4);
echo substr("TaskQuest", -5, 3);

6. Co wyświetli kod?

echo str_replace("a", "o", "banana") . "<br>";
echo ucfirst(strtolower("jAN kOWALSKI")) . "<br>";
echo ucwords(strtolower("jAN kOWALSKI"));

7. Co wyświetli kod?

echo implode(" | ", explode(",", "PHP,SQL,JS"));

8. Która wersja poprawnie wyświetli odnośnik <a href='index.php?id=3'>Europa</a>?

$id = 3;
$nazwa = "Europa";
  • A) echo "<a href="index.php?id=$id">$nazwa</a>";
  • B) echo "<a href='index.php?id=$id'>$nazwa</a>";
  • C) echo '<a href="index.php?id=$id">$nazwa</a>';

9. Które teksty pasują do wzorca /^\d{2}-\d{3}$/?

  • 98-220
  • 98220
  • 98-2201
  • x98-220

10. Funkcja walidujLogin() używa wzorca /^[a-zA-Z0-9_-]{3,20}$/. Co zwróci dla loginów:

  • a) Ala
  • b) Al
  • c) Ala.K
  • d) łukasz
  • e) ola_k-1

11. Co zwróci każde wywołanie?

preg_match('/kot/', 'KOT');
preg_match('/kot/i', 'KOT');
preg_match('/[0-9]{3}/', 'abc12345xyz');

Dlaczego ostatnie wywołanie nie sprawdza, czy tekst to dokładnie trzy cyfry? Jak to poprawić?

12. Uzupełnij wzorzec tak, aby PIN składał się z dokładnie 4 cyfr i niczego więcej:

preg_match('/______/', $pin) === 1

13. Co wyświetli kod (zawartość między nawiasami)?

echo "[" . preg_replace('/\s+/', ' ', "a   b \n c") . "]";

14. Użytkownik wpisał komentarz <b>Hej</b>. Co zobaczy w przeglądarce po echo $komentarz;, a co po echo htmlspecialchars($komentarz);?

15. Która wersja poprawnie sprawdza hasło przy logowaniu? Dlaczego pozostałe nie działają lub są niebezpieczne?

  • A) if (password_hash($haslo, PASSWORD_DEFAULT) == $hash)
  • B) if (password_verify($haslo, $hash))
  • C) if (md5($haslo) == $hash)

16. Zadanie praktyczne (TaskQuest, bez tablic).
Napisz funkcję czyTytulPoprawny(string $tytul): bool, która najpierw czyści tytuł funkcją wyczyscTytul(), a następnie zwraca true, jeżeli tytuł ma od 3 do 100 znaków (licząc polskie litery jako jeden znak). Przetestuj ją dla: " PHP ", " a ", "Nauczyć się regexów".


Odpowiedzi
  1. Cześć $imie i Ola.
  2. 3 7 4 – ż, ó, ł zajmują po 2 bajty.
  3. Wyświetli [ PHP ]. trim() zwraca nowy tekst, a wynik nie został przypisany. Poprawka: $tytul = trim($tytul);.
  4. strpos() zwraca 0 (PHP jest na początku), a 0 w warunku if jest traktowane jak fałsz. Poprawki: if (strpos(...) !== false) albo if (str_contains("PHP jest super", "PHP")).
  5. Quest, Task, Que.
  6. bonono, Jan kowalski, Jan Kowalski.
  7. PHP | SQL | JS.
  8. B. W A cudzysłów w href=" kończy tekst – błąd składni. W C apostrofy nie wstawiają wartości zmiennych – w kodzie strony pojawi się dosłownie $id i $nazwa. Poprawna jest też wersja z łączeniem: echo '<a href="index.php?id=' . $id . '">' . $nazwa . '</a>';.
  9. Tylko 98-220.
  10. a) true, b) false (za krótki), c) false (kropka spoza zbioru), d) false (ł nie należy do a-z), e) true.
  11. 0, 1, 1. Bez ^ i $ wystarczy, że trzy cyfry występują gdziekolwiek w tekście. Poprawka: /^[0-9]{3}$/.
  12. ^\d{4}$ (lub ^[0-9]{4}$).
  13. [a b c] – \s+ zamienia każdą grupę spacji i znaków nowej linii na jedną spację.
  14. Bez htmlspecialchars() przeglądarka zinterpretuje znacznik i wyświetli pogrubione Hej. Z htmlspecialchars() w kodzie strony pojawi się &lt;b&gt;Hej&lt;/b&gt;, a użytkownik zobaczy dosłownie tekst <b>Hej</b> – znacznik nie zadziała.
  15. B. A nie działa, bo password_hash() za każdym razem tworzy inny hash (losowa sól), więc porównanie zawsze da fałsz. C to przestarzały i niebezpieczny sposób – md5() nie służy do przechowywania haseł.
  16. Przykładowe rozwiązanie: function czyTytulPoprawny(string $tytul): bool { $tytul = wyczyscTytul($tytul); $dlugosc = mb_strlen($tytul); return $dlugosc >= 3 && $dlugosc <= 100; } Wyniki: " PHP " → true, " a " → false, "Nauczyć się regexów" → true.