Lekcja 16: Filtrowanie i Walidacja Danych (Filter Functions)
Witaj w szesnastej lekcji kursu PHP! W poprzednich lekcjach, szczególnie przy obsłudze formularzy i przesyłaniu plików, wielokrotnie podkreślaliśmy fundamentalne znaczenie walidacji i oczyszczania danych pochodzących od użytkownika. Jest to absolutnie kluczowy element budowania bezpiecznych i niezawodnych aplikacji internetowych. PHP, od wersji 5.2, dostarcza potężne i elastyczne rozszerzenie "Filter", które znacznie ułatwia ten proces. W tej lekcji dogłębnie zbadamy możliwości funkcji filtrowania, które pozwalają na efektywną walidację (sprawdzanie poprawności) i sanityzację (oczyszczanie) danych wejściowych.
Dlaczego Filtrowanie i Walidacja są Tak Ważne?
Zanim przejdziemy do technicznych szczegółów, przypomnijmy, dlaczego nie możemy ufać żadnym danym przychodzącym z zewnątrz aplikacji (np. od użytkownika przez formularz, z parametrów URL, z plików cookie, czy nawet z zewnętrznych API):
- Bezpieczeństwo: Niewystarczająco zwalidowane lub nieoczyszczone dane są główną przyczyną wielu popularnych ataków webowych, takich jak:
- SQL Injection: Gdy dane użytkownika są bezpośrednio wstawiane do zapytań SQL, atakujący może zmodyfikować zapytanie, aby uzyskać nieautoryzowany dostęp do bazy danych, wykraść dane lub je uszkodzić.
- Cross-Site Scripting (XSS): Gdy dane użytkownika zawierające złośliwy kod JavaScript są wyświetlane na stronie bez odpowiedniego oczyszczenia, kod ten może zostać wykonany w przeglądarce innego użytkownika, prowadząc do kradzieży sesji, phishingu itp.
- File Inclusion (LFI/RFI): Jeśli dane użytkownika są używane do konstruowania ścieżek do plików (np. w
include
lubrequire
), atakujący może próbować dołączyć i wykonać dowolne pliki z serwera lub zdalnie. - Command Injection: Jeśli dane użytkownika są przekazywane do poleceń systemowych, atakujący może próbować wykonać dowolne komendy na serwerze.
- Integralność Danych: Aplikacja powinna przechowywać i przetwarzać dane w oczekiwanym formacie i zakresie. Brak walidacji może prowadzić do zapisywania niepoprawnych, bezsensownych lub uszkodzonych danych w bazie danych, co może zakłócić działanie aplikacji i prowadzić do błędów logicznych.
- Poprawne Działanie Aplikacji (UX): Walidacja pomaga upewnić się, że użytkownik wprowadził dane w poprawnym formacie, co jest niezbędne do prawidłowego działania funkcji aplikacji. Informowanie użytkownika o błędach wejściowych na wczesnym etapie poprawia również jego doświadczenie (User Experience).
Zasada jest prosta: Filtruj dane wejściowe, oczyszczaj dane wyjściowe (Filter Input, Escape Output). Rozszerzenie Filter w PHP skupia się na pierwszej części tej zasady.
Rozszerzenie Filter w PHP
Rozszerzenie Filter jest domyślnie włączone w PHP od wersji 5.2.0. Dostarcza ono zestaw funkcji i stałych zaprojektowanych specjalnie do walidacji i sanityzacji danych. Główne funkcje, z którymi będziemy pracować, to:
filter_var(mixed $zmienna, int $filtr = FILTER_DEFAULT, array|int $opcje = 0): mixed
: Filtruje pojedynczą zmienną za pomocą określonego filtra.filter_input(int $typ_wejscia, string $nazwa_zmiennej, int $filtr = FILTER_DEFAULT, array|int $opcje = 0): mixed
: Pobiera konkretną zmienną zewnętrzną (np. z$_GET
,$_POST
) i opcjonalnie ją filtruje.filter_var_array(array $dane, array|int $definicje_filtrow, bool $dodaj_puste_jako_null = true): array|false|null
: Filtruje wiele wartości z tablicy za pomocą różnych filtrów.filter_input_array(int $typ_wejscia, array|int $definicje_filtrow, bool $dodaj_puste_jako_null = true): array|false|null
: Pobiera wiele zmiennych zewnętrznych i filtruje je.filter_list(): array
: Zwraca listę wszystkich obsługiwanych filtrów.filter_has_var(int $typ_wejscia, string $nazwa_zmiennej): bool
: Sprawdza, czy zmienna o podanej nazwie istnieje w danym źródle wejściowym.filter_id(string $nazwa_filtra): int|false
: Zwraca ID powiązane z nazwanym filtrem.
Typy Filtrów
Filtry w PHP można podzielić na dwie główne kategorie:
- Filtry Walidujące (Validating Filters): Służą do sprawdzania, czy dane spełniają określone kryteria (np. czy są poprawnym adresem e-mail, liczbą całkowitą w danym zakresie itp.). Jeśli dane są poprawne, filtr zazwyczaj zwraca te dane (ewentualnie skonwertowane do odpowiedniego typu, np. string na int). Jeśli dane nie przejdą walidacji, filtr zwraca
false
. Można to zmienić za pomocą flagiFILTER_NULL_ON_FAILURE
, aby w przypadku błędu zwracałnull
. - Filtry Sanityzujące (Sanitizing Filters): Służą do usuwania lub modyfikowania nielegalnych lub niepożądanych znaków z danych, aby uczynić je bezpieczniejszymi lub zgodnymi z oczekiwanym formatem. Te filtry zawsze zwracają przetworzony string, nawet jeśli oryginalny string nie zawierał żadnych znaków do usunięcia/zmiany.
Istnieją również "Inne Filtry" (np. FILTER_UNSAFE_RAW
) oraz możliwość zdefiniowania własnego filtra za pomocą FILTER_CALLBACK
.
Najczęściej Używane Filtry Walidujące
FILTER_VALIDATE_BOOLEAN
: Waliduje wartość jako boolean. Akceptuje "1", "true", "on", "yes" jakotrue
, oraz "0", "false", "off", "no", "" (pusty string) jakofalse
. Zwracatrue
lubfalse
. Można użyć flagiFILTER_NULL_ON_FAILURE
, aby zwracałnull
dla wartości niebędących booleanem.FILTER_VALIDATE_DOMAIN
(od PHP 7.0): Waliduje nazwę domeny zgodnie z RFC 1034, RFC 1035, RFC 952, RFC 1123, RFC 2732, RFC 2181, RFC 1123. FlagaFILTER_FLAG_HOSTNAME
(od PHP 7.0) dodatkowo sprawdza, czy nazwa domeny nie zaczyna się i nie kończy myślnikiem.FILTER_VALIDATE_EMAIL
: Waliduje adres e-mail. Domyślnie używa wyrażenia regularnego, które jest dość liberalne. Od PHP 7.1 można użyć flagiFILTER_FLAG_EMAIL_UNICODE
do walidacji adresów e-mail z międzynarodowymi znakami.FILTER_VALIDATE_FLOAT
: Waliduje wartość jako liczbę zmiennoprzecinkową. Opcjonalnie akceptuje przecinek jako separator dziesiętny (flagaFILTER_FLAG_ALLOW_THOUSAND
jest myląca, chodzi o separator dziesiętny, a nie tysięczny). Można użyć opcjidecimal
do określenia znaku separatora dziesiętnego.FILTER_VALIDATE_INT
: Waliduje wartość jako liczbę całkowitą. Można określić dozwolony zakres (opcjemin_range
,max_range
) oraz czy akceptować liczby ósemkowe (FILTER_FLAG_ALLOW_OCTAL
) lub szesnastkowe (FILTER_FLAG_ALLOW_HEX
).FILTER_VALIDATE_IP
: Waliduje adres IP (zarówno IPv4, jak i IPv6). Można użyć flagFILTER_FLAG_IPV4
lubFILTER_FLAG_IPV6
, aby wymusić konkretny format. FlagiFILTER_FLAG_NO_PRIV_RANGE
iFILTER_FLAG_NO_RES_RANGE
pozwalają wykluczyć adresy prywatne i zarezerwowane.FILTER_VALIDATE_MAC
(od PHP 5.5): Waliduje adres MAC.FILTER_VALIDATE_REGEXP
: Waliduje wartość na podstawie podanego wyrażenia regularnego Perla (opcjaregexp
).FILTER_VALIDATE_URL
: Waliduje adres URL. Domyślnie akceptuje tylko URL-e zgodne z RFC 2396 (np. wymagające schematu http, https, ftp). Można użyć flag, np.FILTER_FLAG_SCHEME_REQUIRED
,FILTER_FLAG_HOST_REQUIRED
,FILTER_FLAG_PATH_REQUIRED
,FILTER_FLAG_QUERY_REQUIRED
.
Najczęściej Używane Filtry Sanityzujące
FILTER_SANITIZE_EMAIL
: Usuwa wszystkie znaki z wyjątkiem liter, cyfr i!#$%&''*+-/=?^_`{|}~@.[]
.FILTER_SANITIZE_ENCODED
: Koduje URL (jakrawurlencode()
). Można użyć flagFILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_LOW
,FILTER_FLAG_ENCODE_HIGH
.FILTER_SANITIZE_MAGIC_QUOTES
: Stosujeaddslashes()
. Przestarzałe, ponieważ magic_quotes zostały usunięte z PHP.FILTER_SANITIZE_NUMBER_FLOAT
: Usuwa wszystkie znaki z wyjątkiem cyfr,+
,-
, oraz opcjonalnie.
,,
,e
,E
. Można użyć flagFILTER_FLAG_ALLOW_FRACTION
,FILTER_FLAG_ALLOW_THOUSAND
(dla separatora tysięcy),FILTER_FLAG_ALLOW_SCIENTIFIC
.FILTER_SANITIZE_NUMBER_INT
: Usuwa wszystkie znaki z wyjątkiem cyfr, znaku plus i minus.FILTER_SANITIZE_SPECIAL_CHARS
: Koduje specjalne znaki HTML (<
,>
,&
,'
,"
) na encje HTML. Podobne dohtmlspecialchars()
. Można użyć flagFILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_ENCODE_HIGH
.FILTER_SANITIZE_STRING
(PRZESTARZAŁE od PHP 8.0, użyjhtmlspecialchars
lub bardziej specyficznych filtrów): Usuwa tagi HTML i opcjonalnie koduje/usuwa znaki specjalne. Flagi:FILTER_FLAG_NO_ENCODE_QUOTES
,FILTER_FLAG_STRIP_LOW
,FILTER_FLAG_STRIP_HIGH
,FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_LOW
,FILTER_FLAG_ENCODE_HIGH
,FILTER_FLAG_ENCODE_AMP
.FILTER_SANITIZE_STRIPPED
: Alias dlaFILTER_SANITIZE_STRING
z flagąFILTER_FLAG_STRIP_HIGH
. (PRZESTARZAŁE od PHP 8.0)FILTER_SANITIZE_URL
: Usuwa wszystkie znaki z wyjątkiem liter, cyfr i$-_.+!*''(),{}|\^~[]`<>#%";/?:@&=
.
Inne Filtry
FILTER_UNSAFE_RAW
: Nie wykonuje żadnego filtrowania. Opcjonalnie można użyć flag (np.FILTER_FLAG_STRIP_LOW
,FILTER_FLAG_ENCODE_HIGH
) do pewnego stopnia oczyszczania. Generalnie należy go unikać, jeśli możliwe jest użycie bardziej specyficznego filtra.FILTER_CALLBACK
: Pozwala na użycie własnej funkcji zwrotnej (callback) do filtrowania. Funkcja callback powinna przyjmować jeden argument (wartość do przefiltrowania) i zwracać przefiltrowaną wartość, lubfalse
jeśli walidacja się nie powiedzie. Opcjaoptions
dla tego filtra powinna zawierać nazwę funkcji callback.
Użycie filter_var()
filter_var()
jest podstawową funkcją do filtrowania pojedynczej zmiennej.
<?php
// Walidacja adresu e-mail
$email1 = "test@example.com";
$email2 = "niepoprawny email";
$valid_email1 = filter_var($email1, FILTER_VALIDATE_EMAIL);
if ($valid_email1 !== false) {
echo htmlspecialchars($email1) . " jest poprawnym adresem e-mail.<br>"; // Wyświetli
} else {
echo htmlspecialchars($email1) . " NIE jest poprawnym adresem e-mail.<br>";
}
$valid_email2 = filter_var($email2, FILTER_VALIDATE_EMAIL);
if ($valid_email2 !== false) {
echo htmlspecialchars($email2) . " jest poprawnym adresem e-mail.<br>";
} else {
echo htmlspecialchars($email2) . " NIE jest poprawnym adresem e-mail.<br>"; // Wyświetli
}
// Walidacja liczby całkowitej z zakresem
$wiek = "25";
$options_int = [
"options" => [
"min_range" => 18,
"max_range" => 65,
"default" => 0 // Wartość domyślna, jeśli walidacja zawiedzie i nie użyto FILTER_NULL_ON_FAILURE
],
"flags" => FILTER_NULL_ON_FAILURE // Zwróci null zamiast false w przypadku błędu
];
$valid_wiek = filter_var($wiek, FILTER_VALIDATE_INT, $options_int);
if ($valid_wiek !== null) {
echo "Wiek $valid_wiek jest poprawny i mieści się w zakresie.<br>"; // Wyświetli
} else {
echo "Wiek ".htmlspecialchars($wiek)." jest niepoprawny lub poza zakresem.<br>";
}
$wiek_niepoprawny = "sto";
$valid_wiek_niepoprawny = filter_var($wiek_niepoprawny, FILTER_VALIDATE_INT, $options_int);
if ($valid_wiek_niepoprawny === null) {
echo "Wartość '".htmlspecialchars($wiek_niepoprawny)."' nie jest poprawną liczbą całkowitą lub jest poza zakresem.<br>"; // Wyświetli
}
// Sanityzacja stringa (przykład z htmlspecialchars, bo FILTER_SANITIZE_STRING jest przestarzały)
$niebezpieczny_string = "<script>alert(\'XSS\');</script> Witaj świecie!";
// Zamiast FILTER_SANITIZE_STRING:
$oczyszczony_string_htmlspecialchars = htmlspecialchars($niebezpieczny_string, ENT_QUOTES, 'UTF-8');
echo "Oczyszczony (htmlspecialchars): " . $oczyszczony_string_htmlspecialchars . "<br>";
// Sanityzacja URL
$url = "http://przykład.com/ścieżka z spacjami";
$sanitized_url = filter_var($url, FILTER_SANITIZE_URL);
echo "Oryginalny URL: " . htmlspecialchars($url) . "<br>";
echo "Sanityzowany URL: " . htmlspecialchars($sanitized_url) . "<br>"; // http://przyklad.com/ciezkazspacjami
// Użycie FILTER_CALLBACK
function walidujKodPocztowy($kod) {
// Prosta walidacja formatu XX-XXX
if (preg_match("/^\d{2}-\d{3}$/", $kod)) {
return $kod;
} else {
return false;
}
}
$kod1 = "12-345";
$kod2 = "12345";
$valid_kod1 = filter_var($kod1, FILTER_CALLBACK, ["options" => "walidujKodPocztowy"]);
$valid_kod2 = filter_var($kod2, FILTER_CALLBACK, ["options" => "walidujKodPocztowy"]);
echo "Kod $kod1: " . ($valid_kod1 ? "Poprawny ($valid_kod1)" : "Niepoprawny") . "<br>"; // Poprawny
echo "Kod $kod2: " . ($valid_kod2 ? "Poprawny ($valid_kod2)" : "Niepoprawny") . "<br>"; // Niepoprawny
?>
Użycie filter_input()
filter_input()
pozwala na pobranie i przefiltrowanie zmiennej bezpośrednio ze źródeł wejściowych, takich jak $_GET
, $_POST
, $_COOKIE
, $_SERVER
, $_ENV
. Jest to często preferowany sposób, ponieważ jasno określa, skąd pochodzą dane.
Pierwszy argument to stała określająca typ wejścia:
INPUT_GET
INPUT_POST
INPUT_COOKIE
INPUT_SERVER
INPUT_ENV
Drugi argument to nazwa zmiennej (klucz w odpowiedniej superglobalnej tablicy).
<?php
// Załóżmy, że URL to: skrypt.php?id=123&email=test@example.com
// Pobranie i walidacja 'id' z GET jako integer
$id_uzytkownika = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
if ($id_uzytkownika === false) {
echo "ID użytkownika jest niepoprawne.<br>";
} elseif ($id_uzytkownika === null) {
echo "Parametr ID nie został przesłany.<br>"; // Jeśli 'id' nie ma w URL
} else {
echo "ID użytkownika: $id_uzytkownika<br>";
}
// Pobranie i sanityzacja 'email' z GET
$email_uzytkownika = filter_input(INPUT_GET, "email", FILTER_SANITIZE_EMAIL);
if ($email_uzytkownika !== null) {
echo "Sanityzowany e-mail: " . htmlspecialchars($email_uzytkownika) . "<br>";
// Dodatkowa walidacja po sanityzacji
if (filter_var($email_uzytkownika, FILTER_VALIDATE_EMAIL)) {
echo "E-mail jest poprawny po sanityzacji.<br>";
} else {
echo "E-mail NIE jest poprawny po sanityzacji.<br>";
}
}
// Przykład dla POST (załóżmy, że formularz wysłał pole 'wiek')
// $wiek_z_post = filter_input(INPUT_POST, "wiek", FILTER_VALIDATE_INT, [
// "options" => ["min_range" => 1, "max_range" => 120],
// "flags" => FILTER_NULL_ON_FAILURE
// ]);
// if ($wiek_z_post === null) {
// echo "Wiek nie został podany, jest niepoprawny lub poza zakresem.<br>";
// } else {
// echo "Wiek z POST: $wiek_z_post<br>";
// }
?>
Jeśli zmienna nie istnieje w danym źródle wejściowym, filter_input()
zwróci null
. Jeśli zmienna istnieje, ale nie przejdzie walidacji, zwróci false
(lub null
, jeśli użyto FILTER_NULL_ON_FAILURE
).
Użycie filter_var_array()
i filter_input_array()
Te funkcje są bardzo przydatne, gdy potrzebujemy przefiltrować wiele wartości naraz. Przyjmują one tablicę definicji filtrów.
Tablica definicji filtrów to tablica asocjacyjna, gdzie klucze odpowiadają kluczom w tablicy danych (dla filter_var_array
) lub nazwom zmiennych wejściowych (dla filter_input_array
). Wartościami mogą być:
- ID filtra (np.
FILTER_VALIDATE_EMAIL
). - Tablica asocjacyjna zawierająca
"filter"
(ID filtra), a opcjonalnie"flags"
i/lub"options"
.
<?php
// Przykład dla filter_var_array()
$dane_wejsciowe = [
"nazwa_produktu" => " <strong>Super Produkt!</strong> ",
"cena" => "99,99", // Używamy przecinka jako separatora
"ilosc" => "10szt",
"email_kontaktowy" => "kontakt@example com",
"strona_www" => "htp://niepoprawny-url"
];
$definicje_filtrow_var = [
"nazwa_produktu" => [
"filter" => FILTER_CALLBACK, // Użyjemy htmlspecialchars jako callback
"options" => function($value) { return htmlspecialchars(trim($value), ENT_QUOTES, 'UTF-8'); }
],
"cena" => [
"filter" => FILTER_VALIDATE_FLOAT,
"options" => ["decimal" => ","], // Określamy, że przecinek to separator dziesiętny
"flags" => FILTER_NULL_ON_FAILURE
],
"ilosc" => [
"filter" => FILTER_VALIDATE_INT,
"options" => ["min_range" => 1],
"flags" => FILTER_NULL_ON_FAILURE
],
"email_kontaktowy" => FILTER_VALIDATE_EMAIL,
"strona_www" => [
"filter" => FILTER_VALIDATE_URL,
"flags" => FILTER_NULL_ON_FAILURE
],
"nieistniejace_pole" => FILTER_VALIDATE_INT // Co jeśli pole nie istnieje w $dane_wejsciowe?
];
$przefiltrowane_dane = filter_var_array($dane_wejsciowe, $definicje_filtrow_var);
echo "<h3>Dane po filter_var_array():</h3><pre>";
print_r($przefiltrowane_dane);
echo "</pre>";
/*
Wynik będzie zawierał:
- nazwa_produktu: oczyszczona
- cena: float 99.99 lub null
- ilosc: int 10 lub null (bo "10szt" nie jest czystym intem, chyba że użyjemy FILTER_SANITIZE_NUMBER_INT najpierw)
W tym przypadku, bez uprzedniej sanityzacji, ilosc będzie null.
Aby to naprawić, można by zrobić: 'ilosc' => ['filter' => FILTER_SANITIZE_NUMBER_INT, 'flags' => FILTER_REQUIRE_SCALAR], a potem walidować.
Lub lepiej, najpierw sanityzacja, potem walidacja w osobnym kroku dla 'ilosc'.
Dla uproszczenia, zmieńmy 'ilosc' w $dane_wejsciowe na "10".
- email_kontaktowy: false (bo niepoprawny)
- strona_www: null (bo niepoprawny i FILTER_NULL_ON_FAILURE)
- nieistniejace_pole: null (bo nie ma go w $dane_wejsciowe i $dodaj_puste_jako_null jest domyślnie true)
*/
// Poprawiony przykład dla 'ilosc' w $dane_wejsciowe:
$dane_wejsciowe_poprawione = $dane_wejsciowe;
$dane_wejsciowe_poprawione["ilosc"] = "10"; // Zmieniamy na poprawny int
$przefiltrowane_dane_poprawione = filter_var_array($dane_wejsciowe_poprawione, $definicje_filtrow_var);
echo "<h3>Dane po filter_var_array() (poprawiona ilość):</h3><pre>";
print_r($przefiltrowane_dane_poprawione);
echo "</pre>";
// Przykład dla filter_input_array() (załóżmy, że dane są w $_POST)
// $_POST = [
// "username" => " Jan Kowalski ",
// "user_age" => "30lat",
// "user_email" => "jan.kowalski@example.com"
// ];
$definicje_filtrow_input = [
"username" => [
"filter" => FILTER_SANITIZE_SPECIAL_CHARS // Użyjmy tego zamiast przestarzałego FILTER_SANITIZE_STRING
],
"user_age" => FILTER_SANITIZE_NUMBER_INT,
"user_email" => FILTER_VALIDATE_EMAIL,
"user_terms" => [
"filter" => FILTER_VALIDATE_BOOLEAN,
"flags" => FILTER_NULL_ON_FAILURE // Jeśli nie ma 'user_terms' w POST, będzie null
]
];
// $przefiltrowane_post = filter_input_array(INPUT_POST, $definicje_filtrow_input);
// echo "<h3>Dane z POST po filter_input_array():</h3><pre>";
// print_r($przefiltrowane_post);
// echo "</pre>";
/*
Jeśli $_POST zawierałoby powyższe dane, $przefiltrowane_post wyglądałoby mniej więcej tak:
[
"username" => " Jan Kowalski " (znaki specjalne byłyby zakodowane, spacje pozostają),
"user_age" => "30" (string, bo sanitize zwraca string),
"user_email" => "jan.kowalski@example.com" (string, bo poprawny email),
"user_terms" => null (bo nie było w $_POST i użyliśmy FILTER_NULL_ON_FAILURE)
]
*/
?>
Domyślnie, jeśli klucz z definicji filtrów nie istnieje w tablicy wejściowej (dla filter_var_array
) lub w źródle wejściowym (dla filter_input_array
), to w wynikowej tablicy pojawi się ten klucz z wartością null
. Można to kontrolować trzecim argumentem tych funkcji: $dodaj_puste_jako_null
(domyślnie true
). Jeśli ustawione na false
, brakujące klucze nie pojawią się w wyniku.
Flagi i Opcje Filtrów
Wiele filtrów można dostosować za pomocą flag i opcji.
Flagi (Flags): Są to stałe (np. FILTER_FLAG_IPV4
, FILTER_FLAG_STRIP_LOW
), które modyfikują zachowanie filtra. Można łączyć wiele flag za pomocą bitowego operatora OR (|
).
Opcje (Options): Są to dodatkowe parametry przekazywane do filtra w postaci tablicy asocjacyjnej. Klucze w tej tablicy to nazwy opcji (np. min_range
, regexp
, default
), a wartości to ich ustawienia.
Przykłady użycia opcji i flag były już pokazane wcześniej. Ważne opcje to m.in.:
default
: Wartość, która ma być zwrócona, jeśli walidacja zawiedzie (używane zamiastfalse
lubnull
, jeśli nie użytoFILTER_NULL_ON_FAILURE
).min_range
,max_range
: DlaFILTER_VALIDATE_INT
iFILTER_VALIDATE_FLOAT
.regexp
: DlaFILTER_VALIDATE_REGEXP
.decimal
: DlaFILTER_VALIDATE_FLOAT
, określa znak separatora dziesiętnego.
Flagi specyficzne dla filtrów:
- Dla
FILTER_VALIDATE_BOOLEAN
:FILTER_NULL_ON_FAILURE
. - Dla
FILTER_VALIDATE_EMAIL
:FILTER_FLAG_EMAIL_UNICODE
(PHP >= 7.1). - Dla
FILTER_VALIDATE_FLOAT
:FILTER_FLAG_ALLOW_THOUSAND
(dla separatora dziesiętnego, nie tysięcy). - Dla
FILTER_VALIDATE_INT
:FILTER_FLAG_ALLOW_OCTAL
,FILTER_FLAG_ALLOW_HEX
. - Dla
FILTER_VALIDATE_IP
:FILTER_FLAG_IPV4
,FILTER_FLAG_IPV6
,FILTER_FLAG_NO_PRIV_RANGE
,FILTER_FLAG_NO_RES_RANGE
. - Dla
FILTER_VALIDATE_URL
:FILTER_FLAG_SCHEME_REQUIRED
,FILTER_FLAG_HOST_REQUIRED
,FILTER_FLAG_PATH_REQUIRED
,FILTER_FLAG_QUERY_REQUIRED
. - Dla filtrów sanityzujących (np.
FILTER_SANITIZE_ENCODED
,FILTER_SANITIZE_SPECIAL_CHARS
, przestarzałyFILTER_SANITIZE_STRING
):FILTER_FLAG_STRIP_LOW
(usuwa znaki ASCII < 32),FILTER_FLAG_STRIP_HIGH
(usuwa znaki ASCII > 127),FILTER_FLAG_STRIP_BACKTICK
,FILTER_FLAG_ENCODE_LOW
,FILTER_FLAG_ENCODE_HIGH
,FILTER_FLAG_NO_ENCODE_QUOTES
(dlaFILTER_SANITIZE_STRING
).
Flagi ogólne (mogą być używane z wieloma filtrami lub w definicjach dla filter_var_array
/filter_input_array
):
FILTER_REQUIRE_SCALAR
: Wymaga, aby wartość była skalarem (int, float, string, bool).FILTER_REQUIRE_ARRAY
: Wymaga, aby wartość była tablicą.FILTER_NULL_ON_FAILURE
: Jeśli walidacja zawiedzie, filtr zwrócinull
zamiastfalse
.
Najlepsze Praktyki
- Zawsze waliduj dane po stronie serwera. Walidacja po stronie klienta (JavaScript) jest tylko dodatkiem dla UX.
- Filtruj dane wejściowe, oczyszczaj dane wyjściowe. Używaj filtrów do walidacji i sanityzacji danych przychodzących. Używaj np.
htmlspecialchars()
przy wyświetlaniu danych na stronie HTML. - Stosuj zasadę białej listy (whitelisting) zamiast czarnej listy (blacklisting). Zamiast próbować blokować wszystkie znane złe wzorce (co jest trudne i podatne na błędy), określaj, jakie dane są dozwolone i akceptuj tylko te.
- Bądź jak najbardziej specyficzny. Wybieraj filtr, który najlepiej pasuje do oczekiwanego typu i formatu danych. Jeśli walidujesz liczbę całkowitą, użyj
FILTER_VALIDATE_INT
z odpowiednimi opcjami zakresu, a nie tylko np.FILTER_SANITIZE_NUMBER_INT
. - Sanityzacja nie zawsze oznacza walidację. Filtr sanityzujący może zwrócić wartość, która nadal nie jest poprawna z punktu widzenia logiki aplikacji (np.
FILTER_SANITIZE_NUMBER_INT
z "abc123def" zwróci "123", co może być poprawną liczbą, ale niekoniecznie tym, czego oczekiwano). Często potrzebne jest połączenie sanityzacji i walidacji. - Rozważ kolejność operacji. Czasami może być konieczne najpierw zsanityzowanie danych (np. usunięcie niepotrzebnych spacji za pomocą
trim()
), a dopiero potem ich walidacja. - Używaj
filter_input()
ifilter_input_array()
, aby jasno określić źródło danych (GET, POST itp.). - Dokładnie testuj swoją logikę walidacji dla różnych przypadków, w tym poprawnych danych, niepoprawnych danych i danych granicznych.
Podsumowanie Lekcji
W tej lekcji dogłębnie poznaliśmy rozszerzenie Filter w PHP, które jest niezwykle użytecznym narzędziem do zapewniania bezpieczeństwa i integralności danych w aplikacjach webowych. Nauczyliśmy się rozróżniać filtry walidujące i sanityzujące, poznaliśmy najważniejsze z nich oraz sposoby ich konfiguracji za pomocą flag i opcji. Przećwiczyliśmy użycie funkcji filter_var()
, filter_input()
, filter_var_array()
i filter_input_array()
. Podkreśliliśmy również kluczowe najlepsze praktyki dotyczące filtrowania i walidacji danych.
Pamiętaj, że solidna walidacja i sanityzacja danych wejściowych to fundament bezpiecznego programowania w PHP. W następnej lekcji zajmiemy się zarządzaniem stanem użytkownika za pomocą sesji i ciasteczek.
Zadanie praktyczne
Stwórz formularz rejestracyjny (rejestracja_form.html
) z następującymi polami:
- Nazwa użytkownika (
username
, tekst) - Adres e-mail (
email
, email) - Hasło (
password
, password) - Powtórz hasło (
password_confirm
, password) - Wiek (
age
, tekst/liczba) - Link do profilu (
profile_url
, url, opcjonalne) - Zgoda na regulamin (
terms
, checkbox)
rejestracja_handler.php
), który odbiera dane z tego formularza (metodą POST) i wykonuje następujące operacje walidacji i sanityzacji używając funkcji filter:
- Nazwa użytkownika: Sanityzuj używając
FILTER_SANITIZE_SPECIAL_CHARS
. Sprawdź, czy nie jest pusta i czy ma np. od 3 do 20 znaków. - Adres e-mail: Zwaliduj używając
FILTER_VALIDATE_EMAIL
. Sanityzuj używającFILTER_SANITIZE_EMAIL
. - Hasło: Sprawdź, czy nie jest puste i czy ma co najmniej 8 znaków. Sprawdź, czy hasło i powtórzone hasło są identyczne. (Nie filtruj hasła samą funkcją filter pod kątem zawartości, tylko sprawdzaj długość).
- Wiek: Zwaliduj jako liczbę całkowitą w zakresie np. 13-100 lat używając
FILTER_VALIDATE_INT
. - Link do profilu: Jeśli podany, zwaliduj jako URL używając
FILTER_VALIDATE_URL
. Sanityzuj używającFILTER_SANITIZE_URL
. - Zgoda na regulamin: Zwaliduj jako boolean (czy checkbox został zaznaczony).
Pokaż przykładowe rozwiązanie
rejestracja_form.html
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Formularz Rejestracji</title>
</head>
<body>
<h2>Rejestracja Użytkownika</h2>
<form action="rejestracja_handler.php" method="POST">
<p><label for="username">Nazwa użytkownika:</label><br>
<input type="text" id="username" name="username" required></p>
<p><label for="email">Adres e-mail:</label><br>
<input type="email" id="email" name="email" required></p>
<p><label for="password">Hasło (min. 8 znaków):</label><br>
<input type="password" id="password" name="password" required></p>
<p><label for="password_confirm">Powtórz hasło:</label><br>
<input type="password" id="password_confirm" name="password_confirm" required></p>
<p><label for="age">Wiek (13-100):</label><br>
<input type="text" id="age" name="age" required></p>
<p><label for="profile_url">Link do profilu (opcjonalne):</label><br>
<input type="url" id="profile_url" name="profile_url"></p>
<p><input type="checkbox" id="terms" name="terms" value="accepted" required>
<label for="terms">Akceptuję regulamin</label></p>
<p><input type="submit" value="Zarejestruj"></p>
</form>
</body>
</html>
rejestracja_handler.php
<?php
$bledy = [];
$danePoprawne = [];
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Definicje filtrów dla filter_input_array
$definicjeFiltrow = [
"username" => FILTER_SANITIZE_SPECIAL_CHARS,
"email" => FILTER_SANITIZE_EMAIL,
"password" => FILTER_DEFAULT, // Hasła nie filtrujemy, tylko walidujemy ręcznie
"password_confirm" => FILTER_DEFAULT,
"age" => FILTER_VALIDATE_INT,
"profile_url" => FILTER_SANITIZE_URL,
"terms" => FILTER_VALIDATE_BOOLEAN
];
$daneWejsciowe = filter_input_array(INPUT_POST, $definicjeFiltrow);
// 1. Nazwa użytkownika
$danePoprawne["username"] = $daneWejsciowe["username"];
if (empty(trim($danePoprawne["username"]))) {
$bledy["username"] = "Nazwa użytkownika jest wymagana.";
} elseif (strlen($danePoprawne["username"]) < 3 || strlen($danePoprawne["username"]) > 20) {
$bledy["username"] = "Nazwa użytkownika musi mieć od 3 do 20 znaków.";
}
// 2. Adres e-mail
$danePoprawne["email"] = $daneWejsciowe["email"];
if (!filter_var($danePoprawne["email"], FILTER_VALIDATE_EMAIL)) {
$bledy["email"] = "Podaj poprawny adres e-mail.";
}
// 3. Hasło
$danePoprawne["password"] = $daneWejsciowe["password"]; // Oryginalne hasło do sprawdzenia
$password_confirm = $daneWejsciowe["password_confirm"];
if (empty($danePoprawne["password"])) {
$bledy["password"] = "Hasło jest wymagane.";
} elseif (strlen($danePoprawne["password"]) < 8) {
$bledy["password"] = "Hasło musi mieć co najmniej 8 znaków.";
} elseif ($danePoprawne["password"] !== $password_confirm) {
$bledy["password_confirm"] = "Hasła nie są zgodne.";
}
// 4. Wiek
$opcjeWieku = ["options" => ["min_range" => 13, "max_range" => 100]];
$wiek = filter_var($daneWejsciowe["age"], FILTER_VALIDATE_INT, $opcjeWieku);
if ($wiek === false) {
$bledy["age"] = "Wiek musi być liczbą całkowitą między 13 a 100.";
} else {
$danePoprawne["age"] = $wiek;
}
// 5. Link do profilu (opcjonalne)
$danePoprawne["profile_url"] = $daneWejsciowe["profile_url"];
if (!empty($danePoprawne["profile_url"]) && !filter_var($danePoprawne["profile_url"], FILTER_VALIDATE_URL)) {
$bledy["profile_url"] = "Podany link do profilu jest niepoprawny.";
}
// 6. Zgoda na regulamin
$danePoprawne["terms"] = filter_var($daneWejsciowe["terms"], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($danePoprawne["terms"] !== true) {
$bledy["terms"] = "Musisz zaakceptować regulamin.";
}
// Wyświetlanie wyników
echo "<h1>Wynik Rejestracji</h1>";
if (empty($bledy)) {
echo "<p style=\"color: green;\">Rejestracja przebiegła pomyślnie!</p>";
echo "<h3>Przesłane dane (po sanityzacji/walidacji):</h3>";
echo "<ul>";
echo "<li>Nazwa użytkownika: " . htmlspecialchars($danePoprawne["username"]) . "</li>";
echo "<li>E-mail: " . htmlspecialchars($danePoprawne["email"]) . "</li>";
// NIE WYŚWIETLAJ HASŁA! Tutaj byłoby hashowanie i zapis do bazy.
echo "<li>Wiek: " . htmlspecialchars((string)$danePoprawne["age"]) . "</li>";
echo "<li>Link do profilu: " . (!empty($danePoprawne["profile_url"]) ? htmlspecialchars($danePoprawne["profile_url"]) : "Nie podano") . "</li>";
echo "<li>Zgoda na regulamin: Tak</li>";
echo "</ul>";
} else {
echo "<p style=\"color: red;\">Wystąpiły błędy w formularzu:</p>";
echo "<ul style=\"color: red;\">";
foreach ($bledy as $klucz => $blad) {
echo "<li><strong>" . htmlspecialchars(ucfirst(str_replace("_"," ", $klucz))) . ":</strong> " . htmlspecialchars($blad) . "</li>";
}
echo "</ul>";
}
} else {
echo "<p>Formularz nie został wysłany.</p>";
}
echo "<p><a href=\"rejestracja_form.html\">Powrót do formularza</a></p>";
?>
Zadanie do samodzielnego wykonania
Napisz skrypt, który przyjmuje przez GET parametr id_produktu
. Użyj funkcji filter do:
- Sanityzacji
id_produktu
tak, aby zawierał tylko cyfry (FILTER_SANITIZE_NUMBER_INT
). - Walidacji zsanityzowanego
id_produktu
jako liczby całkowitej dodatniej (FILTER_VALIDATE_INT
z opcjąmin_range
ustawioną na 1).
?id_produktu=123
, ?id_produktu=abc456def
, ?id_produktu=-5
, ?id_produktu=0
, ?id_produktu=
(pusty).
FAQ - Filtrowanie i Walidacja Danych
Jaka jest różnica między sanityzacją a walidacją?
Walidacja sprawdza, czy dane są zgodne z oczekiwanym formatem/regułami (np. czy to poprawny e-mail, czy liczba mieści się w zakresie). Zwraca informację tak/nie (lub dane/false). Sanityzacja modyfikuje dane, usuwając lub zmieniając potencjalnie niebezpieczne lub niechciane znaki. Zawsze zwraca zmodyfikowane dane.
Czy filtry PHP są wystarczające do pełnego zabezpieczenia aplikacji?
Filtry są bardzo potężnym narzędziem, ale stanowią tylko część strategii bezpieczeństwa. Należy je łączyć z innymi technikami, takimi jak oczyszczanie danych wyjściowych (np. htmlspecialchars
), używanie zapytań przygotowanych (prepared statements) do baz danych, ochrona przed CSRF, odpowiednia konfiguracja serwera i PHP, oraz regularne aktualizacje oprogramowania.
Kiedy używać FILTER_NULL_ON_FAILURE
?
Flaga FILTER_NULL_ON_FAILURE
jest przydatna, gdy chcemy wyraźnie odróżnić sytuację, w której dane nie przeszły walidacji (filtr zwróci null
) od sytuacji, w której poprawne dane mają wartość, która mogłaby być interpretowana jako false
(np. liczba 0, pusty string dla niektórych filtrów walidujących). Ułatwia to pisanie bardziej jednoznacznej logiki warunkowej.
Czy FILTER_SANITIZE_STRING
jest bezpieczny do użycia?
FILTER_SANITIZE_STRING
został oznaczony jako przestarzały (deprecated) w PHP 8.0 i usunięty w PHP 8.1. Miał on pewne problemy i nie był w pełni niezawodny w zapobieganiu XSS we wszystkich kontekstach. Zamiast niego zaleca się używanie bardziej specyficznych filtrów sanityzujących (np. FILTER_SANITIZE_SPECIAL_CHARS
) lub funkcji takich jak htmlspecialchars()
do oczyszczania danych przed wyświetleniem.
Jak walidować bardziej złożone struktury danych, np. tablice zagnieżdżone?
Dla tablic zagnieżdżonych można używać filter_var_array()
z zagnieżdżonymi definicjami filtrów. Kluczem w definicji filtra może być ścieżka do elementu w tablicy. Alternatywnie, można iterować po tablicy i stosować filter_var()
do poszczególnych elementów lub napisać własną funkcję walidującą (ewentualnie używając FILTER_CALLBACK
).
Czy filtry obsługują kodowanie znaków (np. UTF-8)?
Wiele filtrów, zwłaszcza te sanityzujące, działa na poziomie bajtów lub zakłada pewne kodowanie (często ASCII lub ISO-8859-1). Przy pracy z danymi UTF-8, szczególnie przy sanityzacji stringów, należy być ostrożnym. Funkcje takie jak htmlspecialchars()
pozwalają na określenie kodowania (trzeci argument). Dla walidacji (np. e-mail z FILTER_FLAG_EMAIL_UNICODE
) obsługa Unicode jest stopniowo dodawana.
Co jeśli potrzebuję bardzo specyficznej, niestandardowej walidacji?
Możesz użyć FILTER_CALLBACK
i przekazać własną funkcję walidującą. Alternatywnie, możesz połączyć standardowe filtry z własną logiką napisaną w PHP, np. używając wyrażeń regularnych (preg_match
) lub sprawdzając warunki specyficzne dla Twojej aplikacji po wstępnym przefiltrowaniu danych.