czwartek, 29 września 2011

unexpected T_PAAMAYIM_NEKUDOTAYIM

Wpis ku pamięci :)

Komunikat ten (unexpected T_PAAMAYIM_NEKUDOTAYIM) otrzymujemy, gdy używamy :: w kontekście, którego PHP nie obsługuje (out of scope), czyli np. odwołujemy się do zwykłych metod, a nie do metod statycznych. Co ciekawe błąd ten wyskakuje w PHP < 5.3 również gdy wywołujemy istniejącą metodę statyczną przy użyciu zmiennych:
$className::$methodName();
Problem ten oczywiście nie istnieje w PHP >= 5.3, jednak przy starszych wersjach wciąż można natknąć się na tego dziwoląga :)

Alternatywą dla tego rozwiązania jest użycie funkcji call_user_func(), ta jednak działa wolniej od takiego bezpośredniego wywołania:
call_user_func($class."::".$method);
Dla porównania sprawdziłem również czas wykonywania zwykłych (niestatycznych) metod:
class::method				    0.0067858696
call_user_func				    0.0170218945
call_user_func (array)			    0.0149610043
class->method				    0.0044798851
call_user_func (array non-static)	    0.0125558376
Kod z testu do pobrania z chomika (link).

poniedziałek, 26 września 2011

Dzielenie ciągów przy pomocy wielu separatorów

Czasem zdarza się tak, że musimy podzielić nasz ciąg tekstowy w wyznaczonych miejscach na kilka mniejszych. W tej sytuacji funkcja explode() sprawdza się wręcz znakomicie. Problem jednak pojawia się gdy chcemy dzielić za pomocą więcej niż jednego separatora, gdyż samo explode() obsługuje jedynie separator w postaci pojedynczego "stringu". Na poniższych przykładach pokazałem różne sposoby rozbicia ciągu przy użyciu trzech separatorów: & ? /.

Rozwiązania
Najtrywialniejszym sposobem na poprawne rozbicie wg kilku separatorów byłoby wywołanie ponownie explode() dla każdego elementu wynikającego z pierwszego użycia tej funkcji, jednak ten sposób aż bije po oczach swym prostactwem więc nawet nie będę bawił się w jego poprawną implementację, a użyję jedynie takiego (dużego i nierealizującego zadania) przybliżenia:
for ($i = 0; $i < 100000; $i++)
{
	$temp = explode ( "/" , $string);
	foreach($temp as $elem)
	{
		$e = explode("?", $elem);
		$e = explode("&", $elem);
	}
}

Kolejnym pomysłem jest iteracyjne podejście - używanie na przemian explode() i implode() w celu stworzenia pojedynczej tablicy z wynikiem.
for ($i = 0; $i < 100000; $i++)
{
	$temp = explode ( "/" , $string);
	$temp = explode("&", implode("&", $temp));
	$temp = explode("?", implode("?", $temp));
}

Skoro już postanowiliśmy iść w stronę podmian to warto by również rozważyć wielokrotne użycie str_replace().
for ($i = 0; $i < 100000; $i++)
{
	$temp = str_replace("&", "/", $string);
	$temp = str_replace("?", "/", $temp);
	$temp = explode ( "/" , $temp);
}

Nim przejdziemy do wyrażeń regularnych przytoczę tu jeszcze jeden przykład, który niestety działa jedynie w przypadku separatorów będących pojedynczymi znakami. Co ciekawe mimo, iż wykorzystanie funkcji strtok() wydaje się być potwornie nieefektywne to wyniki zbliżone są do naprzemiennego wywoływania funkcji explode() i implode().

for ($i = 0; $i < 100000; $i++)
{
	$tok = strtok($string, "/&?");
	$temp = array();
	while ($tok !== false)
	{
		$temp[] = $tok;
		$tok = strtok("/&?");
	}
	unset($tok);
	strtok('', '');
}

Ale czy mamy ograniczać się jedynie do takich kombinowanych sposobów? Może jest jedna "magiczna" funkcja, która zrobi to o czym myślimy? Ano jest i wykorzystuje magię wyrażeń regularnych, a zwie się ona preg_split().
for ($i = 0; $i < 100000; $i++)
{
	$temp = preg_split("/[\/&?]/",$string);
}

Tutaj jednak warto zwrócić uwagę na fakt, że jeśli naszym separatorem jest znak wielobajtowy to funkcja ta będzie generować dodatkowe puste elementy w wynikowej tablicy (jeden dodatkowy element na każde wystąpienie separatora). Sytuacja ta nie ma jednak miejsca jeśli separatorem nie jest znak a ciąg znaków, nawet jeśli wszystkie zawarte w nim elementy są znakami wielobajtowymi.
Tak czy inaczej remedium na ten problem jest funkcja mb_split():
for ($i = 0; $i < 100000; $i++)
{
	$temp = mb_split("[\/&?]",$string);
}

Ostatnim przytoczonym tu przykładem będzie połączenie wyrażeń regularnych wykorzystanych w funkcji preg_replace() wraz z pojedynczym wywołaniem explode():
for ($i = 0; $i < 100000; $i++)
{
	$temp = preg_replace("/[&?]/","/",$string);
	$temp = explode("/", $temp);
}

Testy wydajności
Jak widać istnieje wiele możliwości wykonania zadania dzielenia ciągu z użyciem wielu równorzędnych separatorów, ale które rozwiązanie jest najefektywniejsze? Popatrzmy na wyniki:

single explode:		    0.1573359966
multiple explode:	    1.0633759499
explode & implode:	    0.7556319237
replace & explode:	    0.4618999958
strtok:			    0.6737470627
preg_split:		    0.9344220161
mb_split:		    1.1594960690
preg_replace & explode:	    1.1028780937

Dla separatorów składających się również z kilku znaków otrzymujemy takie wyniki:

single explode:		    0.1560928822
multiple explode:	    1.3294098377
explode & implode:	    1.1651928425
replace & explode:	    1.0913951397
preg_split:		    1.8065340519
mb_split:		    4.9289889336
preg_replace & explode:	    1.8077979088

Jak widać wszystkie czasy wzrosły, przy czym wzrost mb_split() wynika w dużej mierze z używania wielobajtowych znaków.
Jeśli jednak zostaniemy przy tych trzech jedno-znakowych separatorach, a jedynie zwiększymy rozmiar badanego ciągu otrzymamy takie oto wyniki (10x mniej iteracji pętli testujących):

single explode:		    0.2046608925
multiple explode:	    1.8381199837
explode & implode:	    1.0454828739
replace & explode:	    0.5224039555
strtok:			    1.0597200394
preg_split:		    1.8259940147
mb_split:		    2.1428530216
preg_replace & explode:	    1.8069810867

Jeśli dodatkowo zamiast trzech separatorów użyjemy 5 będziemy mieć takie oto wyniki:

single explode:		    0.2125821114
multiple explode:	    4.1121268272
explode & implode:	    3.3804459572
replace & explode:	    1.6378400326
strtok:			    3.4061551094
preg_split:		    2.7953231335
mb_split:		    6.4811940193
preg_replace & explode:	    3.0788750648

Wnioski
Jak widać wydajność metod różni się w zależności tak od długości badanych ciągów, jak i ilości oraz postaci separatorów. Porównując wyniki możemy jednak dojść do wniosku, że najlepszym rozwiązaniem jest użycie duetu str_replace() wraz z explode(), który to we wszystkich testach okazał się najszybszym rozwiązaniem. Pozostałe metody są w zasadzie porównywalne, choć stosunek ich wydajności różni się w zależności od parametrów zadania (długość ciągu i postać separatorów). Pomimo to możemy jasno stwierdzić, że wielokrotne używanie explode() (multiple explode) jest całkowicie nieefektywne w porównaniu do pozostałych metod (zwłaszcza, że nie zaimplementowano tej funkcji w całości). Podobnie sytuacja wygląda w przypadku mb_split(), którą używać należałoby jedynie w przypadku skomplikowanych podziałów, a nie prostego dzielenia wg podanych słów.

Wykorzystany kod do testowania można pobrać z mojego chomika (link).

niedziela, 18 września 2011

Co zmienia var?

Tematem tego posta będzie znaczenie słówka "var" występującego w JavaScript. Otóż wielu z programistów traktuje po macoszemu deklarację zmiennej, bo wszak w większości przypadków dodanie tych 3 literek nic nie zmienia. Jednak są takie momenty, gdy to słowo kluczowe decyduje o tym, czy skrypt będzie działał poprawnie, czy też będzie skrycie psuł nam krew.
Generalnie problemy z inicjacją zmiennych w JavaScript pojawiają się gdy zaczynamy tworzyć równolegle wykonywane fragmenty kodu, czyli używamy window.setTimeout() oraz window.setInterval(). Szkopuł tkwi w tym, że zmienna utworzona bez użycia "var" zachowuje się jak zmienna statyczna, a dopiero dodanie wspomnianego słówka powoduje, że będzie się ona zachowywać jak normalna zmienna lokalna.

Może jednak pokażę na przykładzie o co mi chodzi :) Przykład będzie trywialny, ot mamy dwa guziki z liczbami, z których pierwszy po kliknięciu ma dodawać "0.1" co 1/10 sekundy, a drugi ma "0.1" odejmować. Oczywiście te 0.1 nie mogą być na sztywno zapisane wewnątrz funkcji.

<script type="text/javascript">

function __countNoVar(node, finalValue)
{
delta = finalValue - Number(node.innerHTML);
delta = Number(Number(delta / 10 ).toFixed(1));

var intervalIdent = self.setInterval(function(){
	    node.innerHTML = (Number(node.innerHTML)+delta).toFixed(1);
	},100);
}

</script>
<button onclick="__countNoVar(this, 1);">0</button>
<button onclick="__countNoVar(this, -1);">0</button>
Dodaje: Odejmuje:

Jak widać jeśli pojedynczo wciśniemy guziki to działają bez problemu, ale w momencie wciśnięcia drugiego buttona nagle również pierwszy zaczyna liczyć w tym samym "kierunku" co ostatnio wciśnięty. Dzieje się tak właśnie z powodu braku "var" przed deklaracją zmiennej "delta", czego wynikiem jest używanie nowej wartości tej zmiennej tak w ostatnio utworzonym "wątku" jak i we wszystkich poprzednich utworzonych za pomocą funkcji "__countNoVar".

Wersja z "varem", która w wyglądzie różni się dosłownie 3 literami działa już jednak poprawnie:
<script type="text/javascript">

function __countWithVar(node, finalValue)
{
var delta = finalValue - Number(node.innerHTML);
delta = Number(Number(delta / 10 ).toFixed(1));

var intervalIdent = self.setInterval(function(){
	    node.innerHTML = (Number(node.innerHTML)+delta).toFixed(1);
	},100);
}

</script>
<button onclick="__countWithVar(this, 1);">0</button>
<button onclick="__countWithVar(this, -1);">0</button>
Dodaje: Odejmuje:

środa, 14 września 2011

Zabezpieczenie najwyższych lotów

Kolega z roku udostępnił dziś pięknego linka do forum iiuj (instytut informatyki Uniwersytetu Jagielońskiego). Nic by w tym nie było ciekawego gdyby nie iście mistrzowski sposób kontroli dostępu.

http://www.ii.uj.edu.pl/dokumenty/weryfikacja.php

Pomijam tu już "skarby" zgromadzone na owym forum.. jednak sam fakt stosowania takich zabezpieczeń przez instytut informatyki uczelni wyższej nie świadczy raczej dobrze o jej administratorze...