printf(" SaltwaterC ");

/dev/urandom

  Archive for the ‘PHP’ Category


Instalarea phpUnit sub Windows 7 / Zend Server Community Edition

Ca de obicei, PHP sub Windows reușește prin varii metode să dea cu mucii în fasole. Anumite chestii ar trebui să meargă OOTB în teorie. În practică apar dificultăți.

În primul rând am golit directorul C:\Program Files (x86)\Zend\ZendServer\bin\PEAR de toate mizeriile mai puțin go-pear.phar, din varii motive, oricum era un “fresh install” de Zend Server CE. C:\Program Files pentru cei ce încă trag cu dinții de 32-bit. După care am purces, folosind un shell rulat sub Administrator:

cd C:\Program Files (x86)\Zend\ZendServer\bin
php -dphar.require_hash=0 PEAR\go-pear.phar
pear channel-update pear.php.net
pear upgrade PEAR
pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install --alldeps phpunit/PHPUnit

PEAR_ENV.reg se generează în C:\Program Files (x86)\Zend\ZendServer\bin. Se adaugă în registru la un simplu dublu click. Rulat sub un simplu user:

C:\Users\SaltwaterC>phpunit
PHPUnit 3.5.11 by Sebastian Bergmann.
 
Usage: phpunit [switches] UnitTest [UnitTest.php]
[...]

Restul e can-can.

Update / Disclaimer: în cazul în care n-am fost suficient de explicit, este vorba de Zend Server Community Edition ca distribuție pe post de purtător de vină. Nu este vorba de PHP / Windows. Dar cum Zend susțin faptul lor că distribuția lor de PHP este “certificată”, “production & development ready”, ca tot omul mă aștept ca anumită funcționalitate să funcționeze fără flotări logice.

Eroarea ține de faptul că o arhivă PHAR are o semnătură ce e fie invalidă, fie lipsește în cazul distribuției respective iar PHP dă un warning și refuză instalarea. Motiv pentru care se aplică la runtime flag-ul phar.require_hash=0. Nu știu dacă e lipsă sau e invalidă, puțin mă interesează. Faptul că installer-ul PEAR din distribuție este vechi a fost doar un bonus. 2008-05-17 – în changelog această dată apare alături de versiunea 1.7.2 pe care o dă Zend “de bună” în ultima versiune, 5.0.4. Scuza că e un pachet comunitar iar dacă nu-mi convine pot să dau $xxxx pentru versiunea comercială nu ține din moment ce Zend se laudă a fi “The PHP Company”. Da, mă deranjează teribil ipocrizia din industria software.

Personal am încetat să mai discut cu cei de la Zend probleme ce țin de anumite alegeri cretine pe care le fac în distribuția lor. Sunt grei de cap și încăpățânați. Singura “victorie” din partea mea a fost includerea unei valori predefinite pentru sendmail_path în distribuția standard. Vezi acest post de prin 2009. Aceasta după ce a trebuit să depun efoturi ca să le explic faptul că /usr/sbin/sendmail este standard pentru toate distribuțiile majore și să mă lupt cu astfel de răspunsuri, după ce ei susțineau sus și tare că nu e nici un bug de configurație faptul că sendmail_path e invalid, deși există n-implementări de MTA ce oferă compatibilitate cu sendmail (personal folosesc postfix, deși postfix pe lângă MTA are și un server SMTP). Ca după aceea să o rezolve până la urmă și să pună un articol pe KB. Îmi este greu să urmăresc logica de a rezolva o problemă pe care inițial susțineai că nu o ai în loc să te ridici de pe curul puturos și să verfici.

Singurul motiv pentru care încă tolerez această distribuție este faptul că oferă pachete binare decente precum update-uri la versiunea de PHP, spre deosebire de anumite distribuții Linux ce au version lock, partea de backports e aproximativ inexistentă, iar un 3rd party oferă mai puțină încredere. Iar ca sysadmin am și altceva de făcut înafară de presupunerea că aș sta să-mi compilez singur serviciile. O fac atunci când nu am alternativă. Chiar și versiunea de PHP din Ubuntu Lucid este jenant configurată, are bug-uri prin ini-uri, etc. Dar măcar ăia nu emit pretenții de distribuție “certificată”.

Având în vedere experiențele proaste cu Zend, slabe șanse să le cumpăr vreodată vreun produs sau să-mi iau “certificare” de la ei din moment ce distribuțiile lor “certificate” nu îndeplinesc un minim nivel al bunului simț.

phbench – PHP Benchmark Test

Obișnuiam să testez varii implementări de stive PHP. Îmi doream un test ceva mai complex ce să vizeze strict PHP. Din nefericire majoritatea chestiilor pe care le-am găsit erau fie prea simple (testau doar operații matematice în bună parte), fie erau prea dificil de instalat. Iar chestiile dificil de instalat nu sunt de mine atunci când vine vorba de munci de astea simple.

La un moment dat am scris phbench, o astfel de soluție, dar rezultatele erau prezentate într-o formă destul de dificilă pentru teste ceva mai îndelungate. Din fericire, mi-a trecut lenea și am rescris complet aplicația, păstrând ideea inițială. Iar acum mi-a trecut lenea de a anunța faptul că de aproape o lună am pus sus noua versiune, de care nu știe nici naiba pentru că nu am mai vorbit de phbench de o vreme. Și am rescris-o folosind Kohana Framework. Iar de această dată este mult mai utilizabilă din moment ce are o interfață AJAX iar rezultatele sunt detaliate și centralizate de către aplicația în sine.

Spre deosebire de versiunea anterioară, lipsește interfața CLI, deși nu știu dacă o voi mai implementa vreodată. De cele mai multe ori am fost pus în situația să-mi pese de cum se mișcă PHP în server side și aproape deloc de cum se mișcă PHP pentru o aplicație CLI.

Printre altele, este și un mini-framework pentru a scrie alte teste. Un nou test este practic o clasă ce respectă niște reguli prestabilite, detaliate în README. Se integrează direct în suportul existent, fără a face configurări de interfațare. phbench folosește reflection pentru aceasta. Sunt deschis la sugestii pentru a include noi teste în distribuția oficială. Cu toate acestea, am evitat teste ce folosesc API-uri externe (gen MySQL, cURL) din moment ce sunt influențate de serverul la care se conectează. Eventual voi face o categorie de teste opționale ce se vor putea include în teste ce pot fi făcute relevante (exemplu: să se conecteze la același server MySQL în teste). Printre altele, deși proiectul nu este o prioritate a mea, voi implementa chestii minore (și mai am vreo 2-3 idei pe rol pentru interfață).

Download.

Și un screenshot:

Lookup Table în PHP

Problema implementării unui lookup table pare uneori simplă. În cazul în care se implementează ca un array indexat, iar obținerea unei valori se face prin metoda dă-mi valoarea Y de la cheia X, atunci problemele de performanță nu intervin.

M-am lovit de altă bubă, și anume implementarea unui lookup table simplu bazat pe căutare liniară construit pe baza unui pre-fetch. Ideea este simplu de implementat, și deși procesul respectiv nu rulează prea des, numărul valorilor distincte complică problema destul de mult. Într-un lookup table de 70.000 – 80.000 valori căutarea unui număr mai mare de valori durează peste jumătate de oră (nu știu exact, am oprit benchmark-ul după 30 minute) cu procesorul (sau un core) la ~100% load, pe când restul operațiilor se execută în secunde. Cam multicel pentru o căutare ce ar trebui să returneze TRUE / FALSE. Faptul că numărul de căutări este mai mare decât lookup table-ul în sine mai bate un cui în coșciugul metodei de mai sus. Se mai adaugă și faptul că deși în teorie valorie ar trebui să fie unice, datele din pre-fetch nu sunt neapărat validate în acest sens.

Pre-fetch – pseudo implementare

$haystack = array();
while($value = get_data())
{
	$haystack[] = $value;
}

Ideea este simplă. Stiva $haystack va conține toate valorile, inclusiv cele duplicat. Mă rog, pre-fetch-ul implementează ideea de “stivă” pentru că auto-indexarea cu [] are aceeași funcționalitate ca și array_push(), dar câștigă puțin la viteza de inserare a datelor. Durează câteva secunde, nu este o operație foarte complexă. De aici începe greul.

Căutare – implementare uzuală

Există tentația de a face căutarea pe baza lui in_array() sau array_search(). Ambele sunt aproximativ la fel de rapide (in_array() este cu maxim 1% mai lentă). Problema principală este căutarea liniară, fără indexare.

Dau chiar exemplele din codul meu de benchmark:

	public function bench_in_array($needle)
	{
		return in_array($needle, $this->haystack);
	}
 
	public function bench_array_search($needle)
	{
		if (array_search($needle, $this->haystack) === FALSE)
			return FALSE;
		return TRUE;
	}

Căutare – soluția optimă

Ambele soluții anterioare sunt aproximativ la fel de rapide, dar aproximativ la fel de lente. Interesul este dacă există o valoare. Duplicatele doar încurcă.

$haystack = array_flip($haystack);

Inversează valoarea cu cheile dintr-un array. Valorile duplicat dispar din moment ce un array (mă rog, devine ordered map) nu poate avea chei duplicat. Se pot aplica alte două metode distincte. Dau din nou exemplu din codul meu de benchmark:

	public function bench_array_flip_isset($needle)
	{
		return isset($this->flipped_haystack[$needle]);
	}
 
	public function bench_array_flip_array_key_exists($needle)
	{
		return array_key_exists($needle, $this->flipped_haystack);
	}

Pentru o listă de $needle scurtă (1% din $haystack, valori 100% valide), sunt aproximativ echivalente. Pentru un input 50% valid de dimensiunea $haystack, array_key_exists() este cu aproximativ 80% – 100% mai lent. Dar acum urmează partea frumoasă: căutarea se face indexat. Metoda este de cel puțin 2000 ori mai rapidă decât căutarea cu in_array() / array_search(). Adică în mod uzual căutarea de mai devreme de peste jumatate de oră durează aproximativ sub 3 secunde cu isset() și sub 5 secunde cu array_key_search(). Metodă testată pe un lookup table de 100.000 valori ce mi-a cam ucis browserul. Chrome a crăpat, Firefox a încărcat output-ul de la Codebench în vreo 3.5 GiB RAM. Atașez și imagini ce stau dovadă atrocității de mai devreme.

Căi UNIX corecte în PHP

Se dă următoarea problemă, aparent mai simplă decât zice titlul: să se determine dacă o cale de tip UNIX este corectă. Buba este faptul că această cale poate să fie către un fișier ce nu există, spre exemplu o cale de UNIX socket ce va fi creat într-un director ce va conține un pool de socket-uri. Deci funcțiile la nivel de filesystem nu vor ajuta, cel puțin nu în primă fază. În cazul  în care există vreo încercare de intuiție a scopului, da, scriu server scripting și în PHP. *sh este prea limitat pentru anumite chestii mai avansate, sau are nevoie de prea mult “boilerplate code”, pe când în Perl nu sunt încă fluent. Pentru cei ce au pierdut știrile de la ora 5, PHP nu mai este de mult un limbaj strict “web oriented”, funcționează bine merci și pentru “general purpose”.

Să revin la problema inițială. Practic se pot identifica două probleme: a) validarea unei căi inexistente – deci apelăm la regex; b) validarea directorului – deși fișierul poate să nu existe în timpul execuției de validare, directorul e musai să fie acolo. De regulă aplicațiile care creează câte un UNIX socket nu verifică aceasta și vor eșua să pornească.

Rezumat, codul arată cam așa:

$validate = preg_match('/^(\/[^\0]*?\/?)[^\0\/]+$/', $path, $matches);
if (empty($validate) === TRUE OR is_dir($matches[1]) !== TRUE OR is_dir($matches[0]) !== FALSE)
{
	// handle error here
}

Acum, discuție pe marginea textului. Acest regex satisface direct ambele probleme. Prima, este fix problema de validare. Se ține cont doar de căi absolute – acesta fiind contextul în care operez. Cu toate acestea, soluția se poate adapta ușor pentru căi relative.

Singurul capturing group din expresie preia întreaga cale, mai puțin numele fișierului. A fost făcută să funcționeze inclusiv pentru root (/). Calea capturată trebuie să fie către un director, pe când calea întreagă trebuie să nu fie un director.

Teoria căilor este simplă. O cale UNIX poate să conțină orice caracter, mai puțin nul, iar / este folosit pe post de separator de directoare, deci este rezervat acestui scop. Căile de tip //var///www//// sunt valide. Exemplul anterior va duce în //var/www unde // în general (Linux, BSD) este tot una cu /. Nu am lucrat pe sisteme unde // să fie distinct față de /, deci nu am considerat că este nevoie să tratez cazul prin expresia de mai sus.

I can has Makavelis numbers

S-a găsit Google Reader să mă scoată cu nasul din shell cu următoarea problemă, dată pe undeva pe aici. Practic rezolvarea problemei:

AB + CD + AC + BD = 100

Ca orice “nerd” ce se respectă, am pus mâna pe Eclipse pentru a rezolva problema, nu pe o foaie de hârtie, din moment ce prin natura ei există 104 ( da, 10.000) soluții posibile, dar doar 25 sunt corecte, considerând faptul că A, B, C pot lua valoarea 0. De fapt, am scris soluția ca pe una configurabilă pentru a-i servi și alt input dacă nu am nimerit-o și A, B, C încep de la 1. Oricum, muncă de 5 minute, dar i-am dat un refactor, să moară dușmanii de ciudă, fără număr la bandă.

Din moment ce propria pacoste de motan mi-a servit drept inspirație, codul e scris lolstyle (dar totuși câtuși de cât profesionist). Ca idee, I_can_has_numbers este clasa ce se ocupă de toate chestiile, iar fișierul I_can_has_everything.php este cel apelabil, fie din browser, pentru cei cu o stivă PHP instalată, fie folosind PHP CLI.

Cod: PHP5/OOP

Download: I_can_has_Makavelis_numbers.zip

Pentru cei născuți cu lene de a mai rula cod, acestea sunt soluțiile:

Number of solutions: 25

A = 0; B = 0; C = 8; D = 6; 0 + 86 + 8 + 6 = 100
A = 0; B = 1; C = 7; D = 6; 1 + 76 + 7 + 16 = 100
A = 0; B = 2; C = 6; D = 6; 2 + 66 + 6 + 26 = 100
A = 0; B = 3; C = 5; D = 6; 3 + 56 + 5 + 36 = 100
A = 0; B = 4; C = 4; D = 6; 4 + 46 + 4 + 46 = 100
A = 0; B = 5; C = 3; D = 6; 5 + 36 + 3 + 56 = 100
A = 0; B = 6; C = 2; D = 6; 6 + 26 + 2 + 66 = 100
A = 0; B = 7; C = 1; D = 6; 7 + 16 + 1 + 76 = 100
A = 0; B = 8; C = 0; D = 6; 8 + 6 + 0 + 86 = 100
A = 1; B = 0; C = 6; D = 7; 10 + 67 + 16 + 7 = 100
A = 1; B = 1; C = 5; D = 7; 11 + 57 + 15 + 17 = 100
A = 1; B = 2; C = 4; D = 7; 12 + 47 + 14 + 27 = 100
A = 1; B = 3; C = 3; D = 7; 13 + 37 + 13 + 37 = 100
A = 1; B = 4; C = 2; D = 7; 14 + 27 + 12 + 47 = 100
A = 1; B = 5; C = 1; D = 7; 15 + 17 + 11 + 57 = 100
A = 1; B = 6; C = 0; D = 7; 16 + 7 + 10 + 67 = 100
A = 2; B = 0; C = 4; D = 8; 20 + 48 + 24 + 8 = 100
A = 2; B = 1; C = 3; D = 8; 21 + 38 + 23 + 18 = 100
A = 2; B = 2; C = 2; D = 8; 22 + 28 + 22 + 28 = 100
A = 2; B = 3; C = 1; D = 8; 23 + 18 + 21 + 38 = 100
A = 2; B = 4; C = 0; D = 8; 24 + 8 + 20 + 48 = 100
A = 3; B = 0; C = 2; D = 9; 30 + 29 + 32 + 9 = 100
A = 3; B = 1; C = 1; D = 9; 31 + 19 + 31 + 19 = 100
A = 3; B = 2; C = 0; D = 9; 32 + 9 + 30 + 29 = 100
A = 5; B = 0; C = 0; D = 0; 50 + 0 + 50 + 0 = 100

 

Update: F (nu o să îi dezvălui numele din moment ce a decis să se semneze doar cu F :) ) a atras antețina asupra faptului că am făcut brute-force în soluția mea sofisticată, deci vin în întâmpinare cu un algoritm bazat pe modelarea matematică a soluției. Algoritmul rezolvă problema în numărul minim de pași (24), plus adăugarea soluției iregulate (unde trișez, este hardcoded). Numărul de bucle s-a redus la două cu număr minim de pași (4 pentru A, inițial 9 pentru B, după care scade, C și D sunt deduse).

Download: I_can_has_Makavelis_numbers_v0.2.zip

Update: din moment ce primii doi algoritmi sunt de cacao (fără o), primul e pur brute-force iar al doilea nu reprezintă o modelare matematică ce să includă toate soluțiile, revin pe scenă cu ultima soluție (finală) a problemei de mai sus.

Sursa algoritmului propriu zis:

for ($a = 0; $a < = 5; $a++)
{
	$double_a = 2 * $a;
	$end_b = 8 - $double_a;
	$d = $a + 6;
	for ($b = 0; $b <= $end_b; $b++)
	{
		$c = (8 - $double_a) - $b;

		if ($d % 10 === 0)
		{
			$a++;
			$d = 0;
		}

		$this->solutions[] = array
		(
			'a' => $a,
			'b' => $b,
			'c' => $c,
			'd' => $d,
		);
	}
}

 

Download: I_can_has_Makavelis_numbers_v0.3.zip


Designed by: studentzFM | Theme made for free by: Casino, punkzFM, and mygroovez | Heavily modified by SaltwaterC

Switch to our mobile site