wtorek, 27 marca 2012

Jak sprawdzać istnienie używanych elementów?

Pytanie brzmi "jak", bo to, że jakoś weryfikować trzeba to mam nadzieję jest oczywiste :) Generalnie rozchodzi się o kilka sytuacji - np. gdy chcemy zinkludować jakiś plik, lub czytamy tablicę i nie jesteśmy pewni, czy jakiś określony klucz jest w niej ustawiony (choćby w przypadku $_POST/$_GET). Oczywiście PHP jako ten nowoczesny język udostępnia nam kilka możliwości sprawdzenia czy faktycznie mamy co czytać, toteż w tym wpisie spróbuję porównać wydajność tych metod.

Elementy tablicy
Na pierwszy ogień pójdzie sprawdzanie istnienia elementów tablicy. By jednak nie było tak całkiem prosto i by było o czym pisać to założymy, że tablica jest wielowymiarowa, czyli składa się z tablic, które składają się z innych tablic:
$arr = array("Nested" => array("Nested" => array("Nested" => "Here")));
Najpopularniejszym sposobem sprawdzania (i generalnie wpajanym wszystkim od maleńkości) czy dany klucz istnieje, jest wykorzystanie funkcji isset(). Tylko, że możemy ją wykorzystać na wiele sposobów...

if(isset())
Dla każdego przykładu użycia dodałem zarówno wywołanie dla elementu w tablicy się znajdującego (Nested), jak i tego, którego w niej nie ma (Indo).
if(isset($arr['Indo']['Indo']['Indo'])) $str = $arr['Indo']['Indo']['Indo'];
if(isset($arr['Nested']['Nested']['Nested'])) $str = $arr['Nested']['Nested']['Nested'];
if & isset (not exist):    0.016935110092163
if & isset (exist):     0.029613971710205
Jak widać PHP szybciej odkryje, że czegoś nie ma... niż, że coś jest :)

Jeden if, wiele isset()
Wydawać by się mogło, że sprawdzenie całej ścieżki w pojedynczym isset() jest o wiele lepszym rozwiązaniem niż wielokrotne wywołanie funkcji...
if(isset($arr['Indo']) && isset($arr['Indo']['Indo']) && isset($arr['Indo']['Indo']['Indo']))
 {} else {}
if & isset (multi - line LR) (not exist):  0.019056081771851
if & isset (multi - line RL) (not exist):  0.021497964859009
if & isset (multi - line LR) (exist):   0.056113004684448
... i niewątpliwie tak jest.. a na pewno jeśli wszystkie wywołania znajdują się w jednym "if". Dlaczego jednak zamieściłem tu ten przykład? Ano dlatego, że można z niego wynieść ważne informacje:
  • PHP sprawdza warunki od lewej do prawej (Left to Right) i kończy obsługę warunku gdy pierwszy z nich == FALSE
    Zatem taki zapis jest bardziej efektywny (po lewej warunki najważniejsze)
    .if(isset($arr['Indo']) && isset($arr['Indo']['Indo']) && isset($arr['Indo']['Indo']['Indo']))
     {} else {}
    niż zapis taki (najważniejsze warunki po stronie prawej)
    if(isset($arr['Indo']['Indo']['Indo']) && isset($arr['Indo']['Indo']) && isset($arr['Indo']))
     {} else {}
  • Sprawdzenie pojedynczego poziomu tablicy jest szybsze niż sprawdzanie kilu poziomów
    if(isset($arr['Indo']['Indo']['Indo'])) {} else {}
    if(isset($arr['Indo'])) {} else {}
    if & isset([][][])(not exist):    0.022554874420166
    if & isset([]) (not exist):    0.016841888427734
    if & isset([][][]) (not exist):   0.032048225402832
    if & isset([]) (not exist):    0.017351150512695

 if i isset() dla każdego poziomu
Taki zapis jest jakby typowym "kaskadowym podejściem" do tego zagadnienia.
if(isset($arr['Indo']))
  if(isset($arr['Indo']['Indo']))
   if(isset($arr['Indo']['Indo']['Indo']))
   {}
Generalnie zaletą tego podejścia jest to, że możemy wcześniej wykryć nieistniejące elementy i nie musimy przeprowadzać analizy całej tablicy. Jeśli jednak nie zależy nam na wykluczeniu istnienia, a jego udowodnieniu, to sens użycia takiego sposobu jest nikły. Do tego dochodzi konieczność obsługi na każdym poziomie kaskady sytuacji nieistnienia elementu.
if & isset (multi - cascade) (not exist):  0.018480777740479
if & isset (multi - cascade) (exist):   0.068065166473389
isset() ? operacja : operacja
Wykorzystanie tej konstrukcji tak na dobrą sprawę ma sens albo gdy chcemy szybciej napisać kod ("? :" to zdecydowanie mniej znaków niż "if() else"), albo gdy do danej zmiennej zamierzamy przypisać jakąś wartość (np. parsowanie danych wprowadzonych przez użytkowników)
isset($arr['Indo']['Indo']['Indo']) ? null : null;
isset & ? (not exist):     0.018883943557739
isset & ? (exist):     0.031083106994629
Bo jak widać pod względem wydajności metoda ta jest nieznacznie gorsza od starego poczciwego if'a.

@element 
Oczywiście poza isset() istnieje również metoda "empiryczna"... a dzięki operatorowi @ możemy ignorować generowane przez PHP błędy (Notice: Undefined index) związane z nieistnieniem elementu, do którego się odwołujemy.
$str = @$arr['Indo']['Indo']['Indo'];
@ (not exist):      0.16418194770813
@ (exist):      0.09358811378479
strict (exist):     0.030779838562012
Tylko, że jak widać czas związany z obsługą błędu jest o wiele większy niż czas potrzebny na przeszukanie tablicy przy pomocy isset().

Pliki
W przypadku plików również mamy kilka możliwości sprawdzenia czy istnieją.. Jednak tutaj opisze tylko trzy: include, file_exists() oraz is_file().

file_exists()
Funkcja ta generalnie służy do sprawdzenia czy dany plik istnieje.
if(file_exists("empty.php")) {} else {}
file_exists (exist):     0.030549049377441
file_exists (not exist):    0.048150777816772
file_exists include (exist):    0.10239315032959
file_exists ? include (exist):    0.10641098022461
file_exists include (not exist):   0.048465013504028

@include
Tym razem znów skorzystamy ze znanego nam już operatora blokującego informacje o błędach.
$var = @include "empty.php";
@include (exist):     0.20435905456543
@include (not exist):     0.073977947235107
Dlaczego przypisujemy to co zwraca include do zmiennej? Ano dlatego, że include zwracać może dowolne wartości, ale domyślnie (jeśli w includowanym pliku nie dodano returna) zwraca TRUE (jeśli plik załadowano) lub FALSE (jeśli pliku nie udało się załadować).

is_file()
Ostatnią zaprezentowaną tutaj metodą jest użycie funkcji is_file(), która to zwraca TRUE jeśli wskazana przez nas ścieżka jest zwykłym plikiem lub FALSE w każdym innym przypadku (czyli jeśli np. wskazuje na folder lub nie wskazuje na nic).
if(is_file("empty.php")) {} else {}
is_file (exist):     0.00079703330993652
is_file (not exist):     0.064265966415405
To co zaskakuje to szybkość z jaką is_file() działa - ok. 40 razy szybciej wykrywa istniejące pliki. Tak więc jeśli szukamy szybkiego sposobu na sprawdzanie czy pliki istnieją - is_file() jest idealnym wyborem. Niestety jego wykrywanie nie istniejących plików jest wolniejsze niż file_exists() - tak więc w zależności od głównego celu naszego bloku kodu powinniśmy wybrać odpowiednią funkcję.

Słowo na koniec
Tak więc jeśli chcemy sprawdzić czy w tablicy znajduje się dany element - to używamy isset(). Jeśli zaś chodzi o pliki, to w zależności od tego czy głównym scenariuszem użycia danego bloku kodu będzie wykorzystanie informacji o istnieniu lub nieistnieniu pliku powinniśmy użyć odpowiedniej funkcji - jeśli chcemy odczytać dany plik to używajmy is_file(), jeśli zaś chcemy stworzyć nowy plik o podanej nazwie - użyjmy file_exists().

A na koniec jak zwykle zapraszam do pobrania pliku z kodem użytym do testów: (link)

Brak komentarzy:

Prześlij komentarz