Multithreading in web browser (Web Workers + jQuery)

This post is a kind of expe­ri­ment. It’s not a strict trans­la­tion of my pre­vious post writ­ten in polish. I’m just curious how “uncle Google” will behave and if he send me some visi­tors :-) Notice that english is my second lan­gu­age, so you will have to bear some my lan­gu­age mista­kes. In case this pro­ject suc­ce­edes, I will write some more english stuff.

I’m going to write about one of HTML5 new featu­res — Web Wor­kers. These are inten­ded to pro­vide some heavy com­pu­ta­tions in the back­gro­und and — what is most impor­tant — web brow­ser rema­ins respon­sive. It means that you won’t expe­rience any slo­we­ness. When Web Wor­ker ends its job, it noti­ces brow­ser about the out­come. The point is to use user’s CPU which is rarely full used in oppo­site to server’s.

By the way, mul­ti­th­re­ading in brow­ser is not a big deal, Face­book uses it to pro­vide you instant mes­sa­ging or wall refre­shing without relo­ading the whole page.

Offi­cial draft of Web Wor­kers API informs that wor­kers have many restric­tions, such as no access to DOM or (in our case) jQu­ery object. You may think of it as a clo­sed envi­ron­ment. Howe­ver you are able to com­mu­ni­cate in both ways via JSON and post­Mes­sage() function.

Take a look at our goal. We can con­trol spaw­ning of new Wor­kers, it state update inte­rvals and the moment of ter­mi­na­tion. To ensure that our brow­ser sup­ports Web Wor­kers, we’ll use nice library spe­cia­li­zed in chec­king a lot of modern HTML featu­res — Moder­nizr. In both cases user reads alert mes­sage if he can use wor­kers or not.

Basicly we need two files: wor­ker and main html file. Below is Java­Script code of timeworker.js:

var workerNo;

onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}

function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}

function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Field wor­kerNo con­ta­ins wor­ker id — inte­ger 1 or greater.
  2. onmes­sage is mes­sage recie­ved from the brow­ser. Notice that we can han­dle recie­ved data via e.data, not e alone!
  3. chan­ge­In­te­rval is func­tion which self calls gim­me­Time() func­tion with given para­me­ter inte­rval (in microsecs).
  4. gim­me­Time sends brow­ser JSON mes­sage with its id and cur­rent time via post­Mes­sage() function.

Now it’s time for workersdemoeng.html (I’m listing only JS code):

var workers = new Array();
var counter = 0;
var bindedWorkers = '';

if (Modernizr.webworkers) {
    alert("Your browser supports Web Workers technology, you're free to test it!");
}
else {
    alert('Your browser does NOT support Web Workers technology. Install yourself a decent browser (FF, Chrome, Opera[?])!');
}

function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}

function fireUpWorker() {
    ++counter;
    var workerDiv = $('<div>').attr('id','workerdiv-'+counter).appendTo('#counters');
    var par = $('<p>').attr('id','worker-'+counter).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+counter+');return false;'
    }).appendTo(workerDiv);
    workers[counter] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[counter].postMessage({timer:timer,workerNo:counter});
    bindWorkers();
}

function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}

function bindWorkers() {
    bindedWorkers += "workers["+counter+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+counter+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. We have 3 glo­bal fields: array wor­kers which holds all active wor­kers, wor­kers coun­ter and bin­de­dWor­kers which con­ta­ins ugly JS code to be exe­cu­ted by eval() — the only way I know to force brow­ser to listen all wor­kers, not only the last one created.
  2. add­No­ti­fi­ca­tion shows short 1,5 sec. noti­fi­ca­tion about wor­ker state update.
  3. fireU­pWor­ker is the main func­tion. Fir­stly it cre­ates a new div, and then para­graph and ter­mi­nate but­ton. Newly cre­ated wor­ker rece­ives JSON mes­sage about its num­ber and state update inte­rval. At the end, bin­dWor­kers() is executed.
  4. ter­mi­na­te­Wor­ker fir­stly kills cer­tain wor­ker by worker.terminate(), and then hides div rela­ted to it.
  5. Finally the func­tion I’m least satis­fied of: bin­dWor­kers(). It han­dles what brow­ser sho­uld do with the rece­ived mes­sage from a wor­ker. It works ana­lo­gi­cally like the worker’s one: thro­ugh onmes­sage and e.data. To bin­de­dWor­kers string we’re appen­ding the code and exe­cu­ting by eval(). The worst part is when we ter­mi­nate some wor­kers, this string con­ta­ins use­less data (we could cut this out by some rege­xps, but I’m lazy ;-) ).

I have a big requ­est for the JS geeks: how to run onmes­sage code in a loop and without eval?

P.S.: It seems that I have found a bug in Chrome brow­ser: an attempt to add onc­lick attri­bute by jQu­ery didn’t wor­ked. You sho­uld use onC­lick instead :-)


Wielowątkowość w przeglądarce (Web Workers + jQuery)

Przed nami cie­kawe czasy w tech­no­lo­gii WWW. Powoli wcho­dzi HTML5 wpro­wa­dza­jąc cał­ko­wi­cie nową jakość. Póki co strony w HTML5 mają cha­rak­ter bar­dziej poka­zowy niż uży­teczny. Ja jako maniak tech­no­lo­gii musia­łem zacząć się z tym wszyst­kim bawić. Na pierw­szy ogień idzie tech­no­lo­gia Web Wor­kers [dalej WW].

WW to próba wpro­wa­dze­nia wie­lo­wąt­ko­wo­ści w prze­glą­darce WWW. W zało­że­niu zada­nia, które wyma­gają dużej mocy obli­cze­nio­wej wrzu­cane są do takiego wor­kera. Gdy sobie skoń­czy liczyć, powia­da­mia prze­glą­darkę, a ta wyświe­tla wynik. Cho­dzi o to, że użyt­kow­nik strony nie­ko­niecz­nie ma ochotę cier­pieć zwol­nie­nie pracy prze­glą­darki w cza­sie kiedy pro­ce­sor jest obcią­żony (ładnie się to nazywa respon­syw­ność przeglądarki).

Zresztą to nie jest jakaś super nowość. Z wąt­kami w tle spo­ty­ka­cie się na co dzień prze­glą­da­jąc Face­bo­oka (powia­do­mie­nia kto jest na cza­cie, nowe posty na tablicy bez prze­ła­do­wy­wa­nia itd.) Ofi­cjalne API WW ma spore obostrze­nia. Wor­ker to jakby zamknięte środo­wi­sko. Nie mamy dostępu do np. obiek­tów strony macie­rzy­stej (DOM, jQu­ery). Wię­cej pole­cam poczy­tać u Mar­co­osa.

Dzi­siaj zro­bimy coś, czego nie widzia­łem nigdzie w Inter­ne­cie póki co. Spró­bu­jemy dyna­micz­nie gene­ro­wać nowe wątki, kaso­wać je, a nawet regu­lo­wać czę­sto­tli­wość ich odpo­wie­dzi. Wszystko to będzie oczy­wi­ście ładnie okra­szone biblio­teką jQu­ery. Sie­dzia­łem nad tym pół nie­dzieli, więc uważajcie!

Naj­pierw rzuć­cie okiem na efekt finalny, a poni­żej wyja­śnie­nia co cie­kaw­szych rzeczy.

Pierw­sze co robimy to spraw­dzamy czy prze­glą­darka użyt­kow­nika w ogóle obsłu­guje WW. Korzy­stamy z biblio­teki Moder­nizr i wyświe­tlamy alert informujący.

Druga sprawa to kod samego wor­kera (timeworker.js):

var workerNo;

onmessage = function(e) {
    workerNo = e.data.workerNo;
    changeInterval(e.data.timer);
}

function changeInterval(interval) {
    setInterval("gimmeTime()",interval);
}

function gimmeTime() {
    postMessage({
        time:new Date().toUTCString(),
        workerNo:workerNo
    });
}
  1. Pole wor­kerNo zawiera iden­ty­fi­ka­tor wor­kera — liczbę od 1 w górę.
  2. onmes­sage to komu­ni­kat ode­brany od prze­glą­darki. Zwra­camy uwagę na to, że do ode­bra­nych danych dobie­ramy się poprzez e.data, a nie samo e!
  3. chan­ge­In­te­rval to funk­cja usta­wia­jąca samo­wy­wo­ła­nie okre­ślo­nej funk­cji cyklicz­nie co podany czas w ms.
  4. gim­me­Time to funk­cja wysy­ła­jące prze­glą­darce JSON z aktu­al­nym cza­sem i swoim iden­ty­fi­ka­to­rem za pomocą funk­cji post­Mes­sage().

Teraz czas na kod workersdemo.html (podaję sam kod Java­Script bo szkoda miejsca).

var workers = new Array();
var licznik = 0;
var bindedWorkers = '';

if (Modernizr.webworkers) {
    alert('Twoja przeglądarka wspiera Web Workers, możesz śmiało testować!');
}
else {
    alert('Twoja przeglądarka NIE wspiera Web Workers, demo nie będzie działało. Zainstaluj sobie prawdziwą przeglądarkę (FF, Chrome, Opera[?])!');
}

function addNotification(workerno) {
    var msg = $('<p>').appendTo('#notif');
    msg.text('Worker '+workerno+' updated state');
    msg.fadeOut(1500);
}

function fireUpWorker() {
    ++licznik;
    var workerDiv = $('<div>').attr('id','workerdiv-'+licznik).appendTo('#liczniki');
    var par = $('<p>').attr('id','worker-'+licznik).appendTo(workerDiv);
    $('<input />').attr({
        type:'submit',
        value:'terminate',
        onClick:'terminateWorker('+licznik+');return false;'
    }).appendTo(workerDiv);
    workers[licznik] = new Worker('timeworker.js');
    var timer = $('#intrv').val();
    workers[licznik].postMessage({timer:timer,workerNo:licznik});
    bindWorkers();
}

function terminateWorker(workerNo) {
    workers[workerNo].terminate();
    $('#worker-'+workerNo).text('terminated!');
    $('#workerdiv-'+workerNo).fadeOut(5000);
}

function bindWorkers() {
    bindedWorkers += "workers["+licznik+"].onmessage = function(event) {";
    bindedWorkers += "$('#worker-"+licznik+"').text('Worker '+event.data.workerNo+' date : '+event.data.time);";
    bindedWorkers += "addNotification(event.data.workerNo);";
    bindedWorkers += "};";
    eval(bindedWorkers);
}
  1. Mamy 3 pola glo­balne: tablicę wor­kers trzy­ma­jącą wszyst­kich aktyw­nych wor­ke­rów, licz­nik wor­ke­rów i bin­de­dWor­kers zawie­ra­jący brzydki kod Java­Script do wyko­na­nia poprzez eval() — jedyny znany mi spo­sób na zmu­sze­nie prze­glą­darki żeby słu­chała wszyst­kich aktyw­nych wor­ke­rów, a nie tylko ostat­niego dodanego.
  2. add­No­ti­fi­ca­tion wyrzuca użyt­kow­ni­kowi komu­ni­kat o ode­bra­niu wia­do­mo­ści od wor­kera, znika po 1,5 sek.
  3. fireU­pWor­ker (nie chciało mi się wysi­lać na lep­szą nazwę ;-) ) to główna funk­cja. Naj­pierw two­rzy nowe ele­menty HTML: div, w nim para­graf na komu­ni­kat z wor­kera i przy­cisk zabi­ja­jący. Nowo stwo­rzo­nemu wor­ke­rowi wysy­łam komu­ni­kat w JSON co jaki czas ma się odświe­żać i jaki ma numer. Na koniec wywo­łuję funk­cję bin­dWor­kers().
  4. ter­mi­na­te­Wor­ker naj­pierw zabija okre­ślo­nego wor­kera poprzez worker.terminate(), a następ­nie ukrywa zwią­za­nego z nim diva.
  5. Na koniec funk­cja, z któ­rej jestem naj­mniej zado­wo­lony: bin­dWor­kers(). Zaj­muje się usta­wia­niem co prze­glą­darka ma robić w przy­padku otrzy­ma­nia komu­ni­katu od wor­kera. Działa to ana­lo­gicz­nie jak poprzed­nio. Poprzez onmes­sagee.data. Do pola bin­de­dWor­kers doda­jemy kod w postaci ciągu i uru­cha­miamy evalem.

Tutaj mam prośbę dla znaw­ców. Doda­wa­nie onmes­sage w pętli dla poszcze­gól­nych wor­ke­rów osobno nie dzia­łało. Jak zała­twić pro­blem bez uży­wa­nia evala?

P.S.: Zna­la­złem chyba buga w Chrome: Próby doda­nia atry­butu onc­lick poprzez jQu­ery nic nie dają. Należy uży­wać onC­lick :-)


Standardy kodowania zgodne z Zend, cz. 3/3

Dzi­siaj ostatni wpis doty­czący pro­gra­mo­wa­nia zgod­nego z Zend. Będzie mowa o doku­men­ta­cji. Sporo infor­ma­cji będzie pocho­dziło z Czy­stego Kodu [dalej CK].

Przede wszyst­kim bloki doku­men­ta­cji muszą być zgodne z for­ma­tem php­Do­cu­men­tor, czyli
/**
* Coś tam dokumentuję...
*/

Współ­cze­sne IDE nie mają pro­blemu z pil­no­wa­niem zgod­no­ści z tym for­ma­tem. Wpi­su­jesz /** naci­skasz Enter i masz ładny blok doku­men­ta­cji. Drugi spo­sób to wszyst­kim znane komen­ta­rze inline two­rzone poprzez // przed tre­ścią komentarza.

Zend Fra­me­work wymaga od nas umiesz­cze­nia doku­men­ta­cji w trzech miej­scach: na początku pliku, przed każdą klasą i przed każdą metodą. W doku­men­ta­cji ZF podano ele­menty, które powinny wcho­dzić w skład blo­ków. Nie będę tego powta­rzał. Zwrócę uwagę tylko na to, że typy para­me­trów i war­to­ści zwra­cane przez funk­cje są rów­nież obligatoryjne.

Więk­szość komen­ta­rzy jest zła. Im szyb­ciej to przy­zna­cie tym lepiej. :-) Zgod­nie z CK komen­ta­rze w więk­szo­ści służą nam do wytłu­ma­cze­nia się z błędów/niedoskonałości naszego kodu. Kod powi­nien być na tyle przej­rzy­sty, żeby komenty nie były wcale potrzebne. Czas poświę­cany na pisa­nie doku­men­ta­cji lepiej prze­zna­czyć na poprawki kodu. Łatwo rzu­cać slo­gany, trud­niej zro­bić. Na pozio­mie jed­nej klasy czę­sto dobry efekt udaje się uzy­skać poprzez roz­par­ce­lo­wa­nie dłu­giej funk­cji na kilka mniej­szych i uży­cie bar­dziej opi­so­wych nazw.

Wielce nie­wska­zana jest redun­dan­cja, czyli nad­mia­ro­wość infor­ma­cji. Jeżeli nazwa funk­cji jest wystar­cza­jąco zna­cząca to rze­czy­wi­ście nie ma sensu powta­rzać tego samego. Raz, że uczy nas pod­świa­do­mego pomi­ja­nia doku­men­ta­cji (no bo po co coś czy­tać skoro jest oczy­wi­ste?), a dwa, że komen­ta­rze czę­sto nie nadą­żają za zmia­nami w kodzie. Stary koment smro­dzi bar­dziej niż jego brak :-) O ile CK doty­czy Javy, a więc sil­nie typo­wa­nego języka (zawsze wiemy jakiego typu jest zmienna czy para­metr i co zwraca metoda), o tyle w PHP musimy uży­wać doku­men­ta­cji do tego celu. Autor CK czę­sto daje znać, że jeżeli kod jest jasny to po co go komentować?

Sporo rze­czy robi za nas sys­tem kon­troli wer­sji. Uni­kamy brzyd­kiego komen­to­wa­nia już nie­po­trzeb­nego kodu oraz nie kusi nas wpro­wa­dza­nie dzien­nika zmian do komen­ta­rzy. W razie potrzeb oglą­damy sobie starą wer­sję pliku lub czy­tamy tekst z com­mitu. Oso­bi­ście uży­wam Baza­ara do swo­ich pro­jek­tów (mini­mum usta­wia­nia, dostępny pod Win­dows i Linux).

Dobry komen­tarz to np. wyróż­nie­nie rze­czy pozor­nie nie­istot­nej, infor­ma­cja co jest jesz­cze do zro­bie­nia (tzw. TODO) lub też przy­kład dopa­so­wa­nia wyra­że­nia regu­lar­nego. Przy­datne jest rów­nież stwo­rze­nie porząd­nego API publicz­nego (wyobra­ża­cie sobie napi­sać cokol­wiek w Javie bez API?)


Standardy kodowania zgodne z Zend, cz. 2/3

W dru­giej czę­ści napi­szę co nieco na temat nazew­nic­twa klas i metod. Jak nazy­wać i czego uni­kać. Do tego na koniec dodam kilka zale­ceń Roberta C. Mar­tina z Czy­stego Kodu [Gli­wice : Helion, 2010].

Klasy

Skład­ni­kiem nazwy klasy nie powi­nien być żaden cza­sow­nik. Mało tego, nie powi­nien to być rów­nież frag­ment nazwy imple­men­to­wa­nego inter­fejsu lub klasy nad­rzęd­nej. Sto­su­jemy Camel­Case począw­szy od dużej litery. Zgod­nie z Czy­stym Kodem lep­sza jest dłuż­sza nazwa klasy/metody niż krót­sza plus komen­tarz wyjaśniający.

W przy­padku pro­gra­mo­wa­nia zgod­nego z Zend nale­ża­łoby nazwę poroz­dzie­lać pod­kreśl­ni­kami pro­wa­dzą­cymi przez struk­turę kata­lo­gów do pliku zawie­ra­ją­cego kod klasy, np. Zend_File_Transfer_Adapter_Http

Pola klas

Krót­kie nazwy zmien­nych typu $i, $j dopusz­czalne są tylko w pętlach lub zmien­nych lokal­nych. Pola klasy rów­nież nazy­wamy zgod­nie z Camel­Case z tym, że roz­po­czy­namy od małej litery. Warte zwró­ce­nia uwagi jest to, że pola i metody pry­watne w kla­sie roz­po­czy­nają się od znaku pod­kre­śle­nia, dzięki czemu od razu widać czy odwo­łu­jemy się do metody publicz­nej czy prywatnej.

Nazwę sta­łych two­rzymy wyłącz­nie za pomocą dużych liter i pod­kreśl­ni­ków. Poni­żej przy­kład wzięty z Zend_Filter
const CHAIN_APPEND  = 'append';
const CHAIN_PREPEND = 'prepend';

Metody

Przy­jęło się kilka kon­wen­cji jeżeli cho­dzi o nazew­nic­two metod. Metody muta­to­rów (tzw. set­tery) i akce­so­rów (tzw. get­tery), czyli usta­wia­jące i pobie­ra­jące zmienne obiektu two­rzymy poprzez setNazwa($nazwa)getNazwa().

Innymi powszech­nie zna­nymi meto­dami są isNazwa()hasNazwa(). Czyli czy obiekt jest czymś lub czy obiekt ma jakąś wła­sność. Muszą one zwra­cać war­tość typu boolean.

Pamię­tamy o tym, że PHP ofe­ruje ogra­ni­czone typo­wa­nie para­me­trów. A w tym zakre­sie, jakie ofe­ruje należy je sto­so­wać, czyli możemy wymu­sić, że przyj­miemy tylko obiekt okre­ślo­nego typu lub imple­men­tu­jący okre­ślony inter­fejs, czyli np. metoda(Trąbka $trąbka) wymusi nam, że obiekt musi być typu Trąbka. Nie­stety typów pro­stych typu int czy bool nie możemy wymu­sić. Zamiast tego pamię­tamy zazna­czyć typ w dokumentacji.

W Zend powszech­nie sto­so­wany jest roz­dział two­rze­nia obiektu od jego ini­cja­li­za­cji za pomocą metody init(). Jeżeli na eta­pie two­rze­nia obiektu coś się wysy­pie i kon­struk­tor wyrzuci wyją­tek, obiekt nie zosta­nie utworzony.

Poni­żej kilka zale­ceń z Czy­stego Kodu doty­czące metod:

  • metoda powinna zaj­mo­wać do ok. 20 linii kodu,
  • powinna wyko­ny­wać jedną operację,
  • metoda bez para­me­trów jest zawsze lep­sza niż z jednym,
  • dopusz­czalne jest do 3 para­me­trów w meto­dzie; jeżeli jest wię­cej tzn. że gdzieś popeł­ni­li­śmy błąd,
  • para­metr true/false naj­czę­ściej ozna­cza, że metoda robi dwie różne rze­czy i naj­le­piej roz­dzie­lić ją na dwie osobne,
  • doda­wa­nie “na pałę” do każ­dego pola pry­wat­nego set­tera i get­tera mija się z celem (po co było usta­wiać zmienną na pri­vate skoro i tak można robić z nią co się chce poprzez settery/gettery),
  • blo­ków try/catch nie należy mie­szać z nor­mal­nym prze­twa­rza­niem, należy je wydzie­lić do osob­nych funk­cji jako mają­cych za zada­nie obsługę błędów

Spodek 2.0, 11 spotkanie — relacja

Naj­lep­sze rela­cje z wyda­rzeń powstają na świeżo, przez co piszę krótką rela­cję kilka godzin po zakoń­cze­niu Spodka 2.0. Ogólne wra­że­nie pozy­tywne. Gdyby nie liczyć nazbyt świe­cą­cego słońca (przez co ledwo było widać pierw­sze dwie pre­zen­ta­cje) i małego zgrzytu na koniec, byłoby cał­kiem fajnie.

Jako pierw­sza była jedna pre­zen­ta­cja wię­cej niż pla­no­wano. Gość z Qualimo.pl, który ją przed­sta­wiał (jeden ze spon­so­rów) spra­wiał wra­że­nie, że nie miał tro­chę na to ochoty, ale to być może tylko takie moje wra­że­nie. Sam pod­czas swo­ich wystą­pień dosyć “cienko śpiewam”.

Drugą pre­zen­ta­cję miał Domi­nik Sza­rek z Webshake.tv. Dawno nie widzia­łem kogoś z taką ilo­ścią ener­gii. Widać było, że rze­czy­wi­ście lubi robić to co robi. Wygląda na to, że będę musiał wygo­spo­da­ro­wać tro­chę czasu na obej­rze­nie wszyst­kich 22 odcin­ków jego vide­oca­stu. Zapa­dło mi w gło­wie fajne zda­nie: [nie cytuję dosłow­nie] “jeżeli tak z utę­sk­nie­niem cze­kasz na week­end to zapisz się na stu­dia zaoczne, zaczniesz tęsk­nić za poniedziałkiem”.

Michał Śliwiń­ski w trze­ciej pre­zen­ta­cji o Nozbe.com opo­wia­dał jak zaczy­nał swój star­tup. Apli­ka­cja “pry­watna”, uła­twia­jąca życie począt­kowo tylko swo­jemu stwórcy stop­niowo została otwarta dla rze­szy użyt­kow­ni­ków. Jego ser­wis znany jest głów­nie na rynku ame­ry­kań­skim (cho­ciażby z powodu braku wer­sji pol­sko­ję­zycz­nej i bycia apli­ka­cją płatną). Wkrótce ma powstać rów­nież wer­sja pol­ska. Sama meto­dyka Get­ting Things Done Davida Allena rów­nież zyskała moje zainteresowanie.

Bar­dzo podo­bała mi się czwarta pre­zen­ta­cja na temat budo­wa­nia zaufa­nia w fir­mie. Oka­zuje się, że do więk­szo­ści ist­nie­ją­cych firm na rynku można by się przy­cze­pić, bo albo próba uzy­ska­nia jakiejś reak­cji ze strony firmy zerowa, a to same super­la­tywy pusz­czane do mediów, a to na stro­nie WWW firmy w dziale kon­takt foto panienki z iStock­Photo, pod­czas gdy pra­cują tam tylko Ste­fan, Kazik i Cze­siek. A wystar­czy­łoby poka­zać swoją twarz i tyle.

Woj­ciech Apel w ostat­niej pre­zen­ta­cji zafun­do­wał nam dwie rze­czy: sporą dawkę wie­dzy prak­tycz­nej z zakresu finan­so­wa­nia star­tu­pów i “wie­czór wybor­czy”. Jeśli cho­dzi o część pierw­szą to nie mam zastrze­żeń. Widać było, że pan wie o czym mówi. Poza tym pre­zen­ta­cja zro­biona w Prezi zawsze robi wra­że­nie :-) Część druga pozo­sta­wiła mały “smro­dek” po spo­tka­niu. Spodek to chyba nie miej­sce na agi­ta­cję poli­tyczną, a to, że jeden ze słu­cha­czy za bar­dzo się “spiął” to też inna sprawa…


Standardy kodowania zgodne z Zend, cz. 1/3

Na stro­nie Zend Fra­me­work macie podane konwencje/standardy doty­czące for­ma­to­wa­nia kodu, nazew­nic­twa i doku­men­ta­cji. Chciał­bym pewne rze­czy roz­sze­rzyć w opar­ciu o wła­sne obser­wa­cje kodu ZF i zale­ce­nia twórców/ekspertów (głów­nie z ich blo­gów). Nie będę oczy­wi­ście bawił się w tłu­ma­cza. Mam nadzieję, że ten 3 czę­ściowy wpis się komuś przyda. Dzielę go z braku czasu…

1. For­ma­to­wa­nie kodu

  • każda klasa w osob­nym pliku, bez znaku koń­czą­cego skrypt ?>
  • nie wolno uży­wać skró­co­nej formy <? ?> lub <% %>
  • wcię­cia mają liczyć 4 spa­cje, bez tabu­la­to­rów (mądrzej­sze edy­tory same zamie­niają taby na spacje)
  • dłu­gość poje­dyn­czej linii kodu powinna wyno­sić poni­żej 80 zna­ków, w pew­nych oko­licz­no­ściach dopusz­czalne jest wię­cej, do 120 zna­ków (abso­lutny limit)
  • nazwa klasy zgodna ze struk­turą kata­lo­gów poprze­dzie­la­nych pod­kreśl­ni­kiem, czyli np. class Spiechu_Captcha, za to plik w tym przy­padku będzie się nazy­wał Captcha.php (jeżeli nie pro­gra­muję w ZF to tego nie uży­wam, ponadto moje pliki koń­czą się na .class.php, np. captcha.class.php
  • bez spa­cji pomię­dzy nazwą funk­cji a para­me­trami, np. function getStringFormatted($string), za to jeżeli jest kilka para­me­trów, to wtedy po prze­cinku jedna spacja
  • jedna spa­cja po struk­tu­rach kon­tro­l­nych typu if else switch i pętlach typu for foreach
  • spa­cja przed i po zna­kach przypisujących/arytmetycznych/logicznych typu = .= + - / % == && ||
  • tzw. one true brace, czyli po dekla­ra­cji klasy i po para­me­trach metod nawias klam­rowy powi­nien zostać prze­nie­siony do nowej linii, za to przy wszel­kich ifach czy forach nie (przy­znaję się bez bicia, nie sto­suję OTB :-) )
  • jeżeli ist­nieje moż­li­wość wymu­sze­nia typu para­me­tru, sto­so­wać to (przy­po­mi­nam, że PHP od wer­sji 5.3 umoż­li­wia zapis function metoda(array $tablica) {coś tam} )
  • uży­wać “podwój­nych pazur­ków” tylko wtedy kiedy to jest konieczne (czyli gdy wsta­wiamy coś ze zmien­nej w locie), a jeżeli już to zawsze uży­wać zapisu w nawia­sach klam­ro­wych, np. $s = "mój kot nazywa się {$kot}";
  • jeżeli jest to moż­liwe, tablica powinna zaczy­nać się od war­to­ści 0 $tablica[0]
  • jeżeli nasz switch prze­wi­duje war­tość default, musi być umiesz­czony jako ostatni (z breakiem!)

Tyle na dzi­siaj. Jeżeli coś sobie przy­po­mnę lub wyczy­tam mądrego, zak­tu­ali­zuję wpis.


Rzut okiem na nowy Filmweb.pl

W marcu w Lookr.tv uka­zał się dwu­czę­ściowy fil­mik przed­sta­wia­jący nową wer­sję Filmweb.pl. Z mniej wię­cej mie­sięcz­nym pośli­zgiem sze­roka publicz­ność może go uży­wać. Na pierw­szy rzut oka widać, że z pier­wo­wzo­rem ser­wis łączy tylko nazwa (i baza fil­mów / użytkowników).

Wygląd

Gra­dient tu, gra­dient tam, wszę­dzie gra­dient! Ogól­nie nie narze­kam. Wolę strony jasne z ciem­nym tek­stem niż na odwrót tak jak to wyglą­dało do tej pory.

Stronę główną zwaną tak jak w Last.fm kok­pi­tem skła­damy sobie z kloc­ków (gadże­tów).  Wszyst­kie gadżety daje się dodać po kilka razy, ale nie wszyst­kie daje się na tyle pousta­wiać żeby poka­zy­wały coś innego. Innymi słowy — po co mam oglą­dać to samo kilka razy? Poza tym nie zapa­mię­tuje mojego układu i w efek­cie środek mam pusty :-)

Sze­ro­kość lay­outu można sobie usta­wić w opcjach. Oprócz war­to­ści pre­de­fi­nio­wa­nych mamy lay­out płynny, który usta­wia się dyna­micz­nie. Fajna sprawa.

Funk­cjo­nal­ność

Od razu muszę się przy­znać, że konto na Film­we­bie było mi potrzebne jesz­cze za sta­rych cza­sów wyłącz­nie do oglą­da­nia tra­ile­rów. Dzi­siaj nie trzeba już tego robić.

Mam wra­że­nie, że w ser­wi­sie mamy kilka miejsc, które podają te same infor­ma­cje tyle, że w inny sposób.

Poza tym wszę­dzie Ajax. Niby faj­nie, że można sobie klik­nąć i natych­miast dosta­jemy odpo­wiedź, ale chyba tro­chę “przedawkowali”.

Usta­wie­nia

Przy oka­zji reje­stra­cji w for­mu­la­rzu reje­stra­cyj­nym nie widzia­łem opcji powtór­nego wpi­sa­nia hasła. Przy zmia­nie hasła już tak. Wydaje się to poważ­nym niedociągnięciem.

Przy usta­wie­niach komu­ni­ka­tora — jeżeli od dawna go nie uży­wamy / zapo­mnie­li­śmy hasła / zli­kwi­do­wano nam konto — nie możemy nic zmie­nić zanim nie prze­ślemy kodu akty­wa­cyj­nego. W efek­cie nie prze­sta­wię sobie komu­ni­ka­tora z chrome.pl na gmail.com.

Gusto­mierz (Trejd Mark)

Bar­dzo fajna sprawa i zara­zem mocno zasta­na­wia­jąca w jaki spo­sób udaje im się obra­biać tak dużą ilość danych użyt­kow­ni­ków. Po oce­nie­niu 50 fil­mów dosta­jemy swoją por­cję reko­men­da­cji zara­zem fil­mów jak i Gusto­po­dob­nych (Trejd Mark).

Od ponad roku podobną funk­cjo­nal­ność ma ser­wis Filmaster.pl. Tam oprócz ogól­nej oceny możemy sobie oce­nić np. reży­se­rię, muzykę czy zdję­cia. Do otrzy­ma­nia reko­men­da­cji wystar­czy oce­nić 20 fil­mów. Liczba użyt­kow­ni­ków jest oczy­wi­ście nie­po­rów­ny­wal­nie mniejsza.

Gusto­mierz (Trejd Mark) uparł się, że będzie poka­zy­wał mi te same reko­men­da­cje aż coś z nimi nie zro­bię, np. zazna­czę, że nie chcę zoba­czyć czy wysta­wię ocenę. Jest to tro­chę dener­wu­jące, bo powi­nien bar­dziej losowo pokazywać.

Zasta­na­wiam się jak oni zro­bili ten sys­tem reko­men­da­cji. Przy­cho­dzą mi do głowy dwa spo­soby gdyby to mi przy­szło wykonać:

  1. Gene­ruję listę np. 100 użyt­kow­ni­ków, któ­rzy oce­nili te same filmy co ja i dali im podobną ocenę (tole­ran­cja –1 / +1 oceny). Następ­nie zli­czam filmy bra­ku­jące w mojej oce­nie (i nie zazna­czone przeze mnie jako “nie chcę oglą­dać”) przez pozo­sta­łych oce­nione na wyso­kiej pozy­cji. Pozo­staje pro­blem skali: jeżeli użyt­kow­ni­ków w bazie mamy kilka tys. (i ocen fil­mów kil­ka­dzie­siąt tys.), to jesz­cze ten spo­sób da się zre­ali­zo­wać. A co jeżeli tak jak w Film­we­bie mamy kilka mln. użyt­kow­ni­ków? Kil­ka­na­ście ser­we­rów SQL zajeż­dża bazę dzień i noc?
  2. Robię coś w rodzaju wewnętrz­nych tagów, tzn. opi­suję każdy film kil­ku­dzie­się­cioma znacz­ni­kami, np. II Wojna Świa­towa, Azja, podróż w cza­sie, średnio­wie­cze itd. Listę reko­men­da­cji gene­ruję na pod­sta­wie ilo­ści wspól­nych tagów w oce­nio­nych przeze mnie fil­mach i zawie­ra­ją­cych ich naj­wię­cej w pro­po­no­wa­nych. Tutaj pro­ble­mem jest tyta­niczna praca tagu­ją­cych filmy. Dla 100 fil­mów jeste­śmy w sta­nie coś takiego zro­bić, a dla kil­ku­dzie­się­ciu tys.? A może popro­sić o pomoc użytkowników?

Reasu­mu­jąc, Film­web poszedł w dobrą stronę. Jeżeli będą słu­chać opi­nii użyt­kow­ni­ków i popra­wią nie­do­róbki, to będzie jesz­cze lepiej. Chwi­lowe zadyszki ser­we­rów tak bar­dzo nie prze­szka­dzają. Gdzie te czasy co strona była dostępna tylko po pół­nocy i rano?

Zwra­cam jesz­cze uwagę na inną rzecz. W Fil­ma­ste­rze całą treść mamy na “naj­lżej­szej” licen­cji Cre­ative Com­mons — Uzna­nie Autor­stwa, pod­czas gdy Film­web ma pełny Copy­ri­ght + Trade Marki (z któ­rych się tro­chę nabi­jam). Zasta­nów­cie się który ser­wis lepiej trak­tuje swo­ich użyt­kow­ni­ków? Dla mnie jako miło­śnika Cre­ative Com­mons wybór jest prosty.


Własne funkcje w Google Spreadsheets

Od cza­sów powsta­nia Google Docs (a szcze­gól­nie Spre­ad­she­ets) zasta­na­wia­łem się czy da się zro­bić w tym coś kon­kret­nego. Mam tu na myśli wła­sne funk­cje, jakieś wykresy, a nie tylko “wylicz mi sumę z pól A1:A5”. Doku­men­ta­cja pra­wie tylko po angiel­sku, do tego jesz­cze czę­sto mało obszerne infor­ma­cje na temat np. tytu­ło­wych custom func­tions.

Jako iż wynaj­muję miesz­ka­nie, co mie­siąc muszę poświę­cać 10 min. aby wyli­czyć wła­ści­cie­lowi należną mu kwotę za media. To o 9 min. za dużo! A pomy­lić się po dro­dze jesz­cze można… Uła­twi­łem sobie two­rząc arkusz, który wymaga tylko wpi­sa­nia liczby z licznika.

Stwo­rzymy sobie arkusz kal­ku­la­cyjny w Google Spre­ad­she­ets (jeżeli oczy­wi­ście komuś nie prze­szka­dza, że Google oprócz tego co ma w mailach i czego szuka, będzie rów­nież wie­działo ile zużywa cie­płej wody itd.)

Udo­stęp­ni­łem wszyst­kim “arkusz badaw­czy” do wglądu. Może­cie oglą­dać, ale nie może­cie nic zmie­niać (zaraz bym tam czy­tał o powięk­sza­niu peni­sów). Natu­ral­nie powpi­sy­wa­łem losowe war­to­ści, to się nie suge­ro­wać :-)

Wła­sne funk­cje w G. Docs możemy sobie pisać w Java­scrip­cie. Tools -> Scripts -> Script Edi­tor. W moim przy­padku zro­bi­łem funk­cję wyli­cza­jącą mie­sięczną opłatę za kon­kretne medium. Wyglada tak:

function liczMedium(stalaOplata, cenaJednostkowa, staryStan, nowyStan) {
  if (staryStan == '' || nowyStan == '') return 'czekam na dane...';
  else if (typeof staryStan != 'number' || typeof nowyStan != 'number') return 'stany licznika musza byc liczba!';
  if (cenaJednostkowa == '') return 'Nie podano ceny jednostkowej!';
  stalaOplata = (stalaOplata == '')?0:stalaOplata; // nie kazde medium ma opate przesylowa
  stalaOplata = parseFloat(stalaOplata); // rzutuj do floata
  cenaJednostkowa = parseFloat(cenaJednostkowa); // rzutuj do floata
  var roznica = parseFloat(nowyStan - staryStan); // roznice tez do floata
  if (roznica < 0) return 'Błąd - nowy stan licznika nie może być mniejszy od starego';
  return stalaOplata + (cenaJednostkowa * roznica); // dla kazdego medium takie samo dzialanie
}

Te dziwne machloje z rzu­to­wa­niem do flo­atów ceny jed­nost­ko­wej są przez to, że Google uparło się, że wszystko licz­bowe w komórce skon­wer­tuje do postaci z dwoma miej­scami po prze­cinku. Wiemy, że np. cena za KWh prądu to zazwy­czaj coś takiego jak 0,3567545487566575676 zł i trzeba użyć pola tek­sto­wego :-)

Oczy­wi­ście nie­ład­nie by było gdyby funk­cja nie dosta­jąc żadnej war­to­ści wyrzu­cała szpetny error. Dla­tego jeżeli nic nie jest wpi­sane, wyświe­tla w komórce cze­kam na dane… Dodat­kowo pod sam koniec przy wyli­cza­niu róż­nicy robimy pro­ste spraw­dze­nie czy ktoś nie “mach­nął się” odczy­tu­jąc z licz­nika mniej­szą war­tość od poprzed­niej. Zapewne od razu zapy­ta­cie: a co jak mi wymie­nili licz­nik? Na mój gust trzeba by wpi­sać jed­no­ra­zowo jakiś off­set przy przej­ściu ze sta­rego licz­nika na nowy, np.: =liczMedium(E6;F6;G5;475+G6).

Zuży­cie roczne wyli­czam poprzez =(MAX(G3:G14)-MIN(G3:G14), czyli szu­kam mak­sy­mal­nej i mini­mal­nej liczby i odej­muję. Jeżeli zmie­nili nam licz­nik to znowu będziemy mieli zafał­szo­wane dane. Należy wydzie­lić zakres sta­rego licz­nika i nowego np. tak: =(MAX(G3:G5)-MIN(G3:G5))+(MAX(G6:G14)-MIN(G6:G14)).

Na koniec zapewne zapy­ta­cie jak zro­bić taki do niczego nam nie­po­trzebny baje­rancki wykres? Ano żeby go zro­bić, potrze­bu­jemy zgru­po­wać poroz­rzu­cane dane w jed­nej tabelce. Naj­le­piej zro­bić to w osob­nym arku­szu (u mnie Sheet2) lin­ku­ją­cym do komó­rek z pierw­szego arku­sza. Aby odwo­łać się wpi­su­jemy =Sheet1!A1. Wykres z kolei lin­kuje do zakresu z dru­giego arku­sza =Sheet2!A1:E13

Jak coś jest nie­zro­zu­miałe to pytać. Wpis dedy­kuję wszyst­kim “wynaj­mo­wa­czom” nie­uto­pio­nym w doży­wot­nim kre­dy­cie hipo­tecz­nym, peace!


Po co ci ten telewizor?

Może jest to dziwne dla nie­któ­rych, ale celowo nie posia­dam tele­wi­zora ani radia już pra­wie rok. Z początku miało to być chwi­lowe (prze­pro­wadzka), ale stało się tro­chę prze­pi­sem na życie. Czas tra­cony przed tele­wi­zo­rem zamie­ni­łem na tra­cony w Inter­ne­cie i jest mi z tym dobrze. Zapy­ta­cie jaka to róż­nica? Zasad­ni­cza: to ja decy­duję kiedy, co i jakiej jako­ści treść oglądam.

Nie­liczne war­to­ściowe rze­czy z TVP (doku­menty) oglą­dam poprzez tvp.pl, które są tam za darmo. Zna­jomi mówią mi, że jestem uza­leż­niony od Inter­netu. Sami zapo­mi­nają nato­miast, że są uza­leż­nieni od idio­tycz­nych pro­gra­mów w tele­wi­zji typu “śpie­waj z gwiaz­dami, tańcz i walcz”. Mam wra­że­nie, że po obej­rze­niu jed­nego odcinka mój mózg doznaje jakichś mikro­usz­ko­dzeń (całe szczę­ście wraca po cza­sie do normy).

Czy ktoś nor­malny ogląda te wszyst­kie debilne seriale? Wyima­gi­no­wane pro­blemy wyima­gi­no­wa­nych ludzi. Jedyna war­tość jakiejś “Ple­ba­nii” to pod­stawa pod pro­duct pla­ce­ment. Potem akto­rzy takich pro­duk­cji mają się za gwiazdy i stają się cele­bry­tami. Widać ich w kolej­nych edy­cjach “tań­czą­cych…” W moim przy­padku jest tak, że nie potra­fię odróż­nić gwiazdy od tan­ce­rza. Widać nie tylko ja mam taki pro­blem.

Narze­kasz na poziom tele­wi­zji? Prze­stań ją oglą­dać! Idź na piwo ze zna­jo­mymi czy poczy­taj jakąś książkę. Rekla­mo­dawcy nie będą pła­cić za nie­oglą­dane pro­gramy, przez co nie będzie opła­calna ich pro­duk­cja. Poza tym wła­ści­ciele tele­wi­zji świet­nie wie­dzą kto co ogląda. Dopóki okre­ślony pro­cent danej grupy wie­ko­wej ogląda ten szajs, dopóty pozo­staje sta­tus quo i wszy­scy są zado­wo­leni oprócz czę­ści bar­dziej myślą­cych widzów.

Nie tole­ruję pogła­śnia­nych, emi­to­wa­nych co 20 min. do znu­dze­nia dur­nych reklam w trak­cie emi­sji pro­gra­mów. Jak dla mnie wystar­cza wideo na żądanie. Póki co, oferta tele­wi­zji przez Inter­net nie jest zbyt duża, ale myślę, że będzie się to z cza­sem zmie­niać. Oso­bi­ście kibi­cuję iplex.pl. Kon­ku­ren­cję powi­tam z otwar­tymi rękami.

Jesz­cze jedna sprawa: lubisz dosto­so­wy­wać roz­kład swo­jego dnia pod tele­wi­zję? Ja nie.

Wpis powstał pod wpły­wem tro­chę nie­chluj­nego gra­ficz­nie, ale tra­fia­ją­cego w sedno wykopu.


Zend Framework i podświetlanie wybranego elementu menu

W życiu każ­dego pro­gra­mi­sty nad­cho­dzi chwila, od któ­rej staje się leniwy (czy­taj: mądrzej­szy) i zaczyna korzy­stać z goto­wych, wydaj­niej­szych roz­wią­zań w postaci fra­me­wor­ków. Do tej pory pro­gra­mi­sta two­rzył w pocie czoła swoje “klocki”, z któ­rych mógł sobie skła­dać apli­ka­cje. W lite­ra­tu­rze zwie się takie coś “reinven­ting the wheel”, czyli po naszemu tra­ce­nie czasu na kle­pa­nie kodu, który ktoś napi­sał za nas. Śpie­chu niniej­szym prze­cho­dzi na następny level i zaczyna pozna­wać Zend Fra­me­work.

Nie ma nie­stety dobrej lite­ra­tury w języku pol­skim do Zenda. Przy oka­zji cze­ka­nia na pociąg zaha­czy­łem o Empik i kupi­łem sobie PHP5. Pro­gra­mo­wa­nie z wyko­rzy­sta­niem Sym­fony, Cake­PHP, Zend Fra­me­work Tomasz Ska­ra­czyń­skiego i Andrzeja Zoła. 2/3 książki zaj­muje nie­stety opis Sym­fony. Myślę jed­nak, że te kil­ka­dzie­siąt stron na start z Zen­dem wystar­czy. Potem wgry­zamy się w doku­men­ta­cję na stro­nie producenta.

W skró­cie: Zend Fra­me­work sto­suje wzo­rzec pro­jek­towy Model-Widok-Kontroler (MVC). Na świat wysta­wiony mamy jedy­nie plik index.php. Router roz­pra­co­wuje żądanie wyświe­tle­nia strony i kie­ruje do odpo­wied­niego kon­tro­lera i jego akcji. Jeden kon­tro­ler może mieć wiele akcji, z któ­rych każda może robić co innego. Dla każ­dego kon­tro­lera przy­pi­sany jest osobny kata­log z wido­kami, z któ­rych gene­ro­wana jest osta­teczna strona. Kon­tro­ler w razie potrzeby odpala rów­nież jakieś zapy­ta­nia do bazy danych (model) i prze­ka­zuje wyniki do widoku.

Chcemy uzy­skać taki efekt, że po klik­nię­ciu kon­kret­nego ele­mentu menu ten nam się pod­świe­tli. I to tak żeby cała ope­ra­cja odby­wała się bez inge­ren­cji pro­gra­mi­sty piszą­cego kolejne akcje. Samego startu z Zen­dem nie będę oma­wiał. Zapy­taj­cie wujka Google o Zend_Layout i o roz­sze­rza­nie Zend_Controller_Plugin_Abstract.

Pierw­sze czego potrze­bu­jemy to plik layout.phtmlapplication/layouts/scripts. Jest super pro­sty i zawiera 2 pla­ce­hol­dery — 1 na menu i 1 na treść z akcji.

<html><head></head>
<body>
   <div id="menu">
      <?php echo $this->layout()->menu; ?>
   </div>
   <div id="content">
      <?php echo $this->layout()->content; ?>
   </div>
</body>
</html>

Następ­nie potrze­bu­jemy kon­tro­lera, który będzie zaj­mo­wał się tylko wyświe­tla­niem menu. Two­rzymy plik MenuController.phpapplication/controllers.

class MenuController extends Zend_Controller_Action {
   public function init() {
      $this->_helper->viewRenderer->setResponseSegment('menu');
   }
   public function showmenuAction() {
      $menuItems = array(
         array('tytul' => 'pierwszy link',
            'kontroler' => 'index',
            'akcja' => 'pierwszy'),
         array('tytul' => 'drugi link',
            'kontroler' => 'index',
            'akcja' => 'drugi'),
         array('tytul' => 'trzeci link',
            'kontroler' => 'index',
            'akcja' => 'trzeci')
      );

      $this->view->assign('menu', $menuItems);
      $front = Zend_Controller_Front::getInstance();
      $this->view->assign('highlight_action', $front->getPlugin('Spiechu_Menu_Plugin_Menu')->getActionName());
      $this->view->assign('highlight_controller', $front->getPlugin('Spiechu_Menu_Plugin_Menu')->getControllerName());
   }
}

Powyż­szy kod usta­wia pla­ce­hol­der na menu (con­tent jest domyślny), następ­nie prze­ka­zuje zmienne potrzebne do wyświe­tle­nia widoku.

Teraz trzeba by jakoś wyświe­tlić te ele­menty menu. Two­rzymy plik widoku odpo­wia­da­jący nazwie akcji. W naszym przy­padku jest to showmenu.phtmlviews/scripts/menu.

<ul>
 <?php foreach ($this->menu as $menuItem): ?>
  <?php if ($this->highlight_action == $menuItem['akcja'] && $this->highlight_controller == $menuItem['kontroler']): ?>
   <li><a style="background-color:green;" "href="<?php echo $this->url(array('controller' => $menuItem['kontroler'] , 'action' => $menuItem['akcja'])); ?>"><?php echo $menuItem['tytul'] ?></a></li>
  <?php else: ?>
   <li><a href="<?php echo $this->url(array('controller' => $menuItem['kontroler'] , 'action' => $menuItem['akcja'])); ?>"><?php echo $menuItem['tytul'] ?></a></li>
  <?php endif; ?>
 <?php endforeach; ?>
</ul>

Powyż­sza hybryda php i html ite­ruje nam po wszyst­kich ele­men­tach tablicy $this->menu. Jeżeli aktu­al­nie ite­ro­wany ele­ment odpo­wiada temu do pod­świe­tle­nia to wkle­jamy mu styl background-color pod­świe­tla­jący na zie­lono. Warty uwagi jest rów­nież hel­per widoku $this->url(), który mon­tuje nam pra­wi­dłowy adres do danego ele­mentu menu.

Zapewne zwró­ci­li­ście uwagę na takie dziwne coś jak Spiechu_Menu_Plugin_Menu. Jest to mój plu­gin pod­cze­pia­jący się do kon­tro­lera fron­to­wego i pobie­ra­ją­cego z niego dane na temat żądanego kon­tro­lera i akcji. A czemu taka dziwna nazwa? Zend roz­pra­co­wuje sobie gdzie ma szu­kać danej klasy w struk­tu­rze kata­lo­gów za pomocą nazw poprze­dzie­la­nych podkreślnikiem.

Two­rzymy kod plu­ginu w library/Spiechu/Menu/Plugin/Menu.php

class Spiechu_Menu_Plugin_Menu extends Zend_Controller_Plugin_Abstract {
 protected $_actionName;
 protected $_controllerName;

 public function routeShutdown(Zend_Controller_Request_Abstract $request) {
  $this->_actionName = $request->getActionName();
  $this->_controllerName = $request->getControllerName();
  $menu = clone $request;
  $menu->setControllerName('menu')->setActionName('showmenu');
  $frontController = Zend_Controller_Front::getInstance();
  if (!$frontController->hasPlugin('Zend_Controller_Plugin_ActionStack')) $frontController->registerPlugin(new Zend_Controller_Plugin_ActionStack());
  $actionStack = $frontController->getPlugin('Zend_Controller_Plugin_ActionStack');
  $actionStack->pushStack($menu);
 }

 public function getActionName() {
  return $this->_actionName;
 }

 public function getControllerName() {
  return $this->_controllerName;
 }
}

Macie tutaj sporo nie­zro­zu­mia­łego kodu. Dla każ­dego plu­ginu po zakoń­cze­niu pro­cesu routingu, a przed roz­po­czę­ciem pętli wyko­ny­wa­nia akcji odpa­lana jest m.in. metoda route­Shut­down(). Uży­wamy jej do zapisu danych nt. żądanego kon­tro­lera i akcji, a następ­nie wrzu­camy na stos (Zend_Controller_Plugin_ActionStack) dodat­kową akcję do wyko­na­nia, która wyświe­tli nam menu. Jest tutaj użyty numer z klo­no­wa­niem, który gdzieś zna­la­złem na forum. Potrze­bu­jemy jakie­goś żądania, które można prze­flan­co­wać na wyświe­tle­nie menu. Ponadto nie możemy popsuć tego wła­ści­wego, więc uży­wamy klonowania.

Ostat­nie czego potrze­bu­jemy to zare­je­stro­wać plu­gin, gdyż Zend nie wie czy chcemy go uży­wać czy nie. Naj­le­piej to zro­bić w pliku application/bootstrap.php. U mnie wygląda to tak:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
 public function __construct($application) {
  parent::__construct($application);

  Zend_Loader_Autoloader::getInstance()->registerNamespace('Spiechu_');
  $frontController = Zend_Controller_Front::getInstance();
  $frontController->registerPlugin(new Spiechu_Menu_Plugin_Menu());
 }
}

Naj­pierw reje­stru­jemy nową ścieżkę, a następ­nie reje­stru­jemy plu­gin. Jak zapewne zauwa­ży­li­ście, Zend_Loader_Autoloader jak i Zend_Controller_Front to sin­gle­tony. Mamy przez to pew­ność, że gdy w kon­tro­le­rze chcemy wycią­gnąć dane z naszego plu­ginu, to jest to ta sama instancja.

Na razie wszystko to jest dla mnie dosyć nowe. Sam nie do końca czuję czemu to nie wyrzuca jakie­goś błędu. :-)

Myślę, że ten wpis to dopiero począ­tek pisa­nia o Zendzie.

PS.: Nie zapo­mnij­cie sobie stwo­rzyć akcji pierw­szy­Ac­tion(), dru­giAc­tion(), trze­ciAc­tion() w kon­tro­le­rze Inde­xCon­trol­ler (może wyświe­tlać tylko 1, 2, 3 — ważne, żeby można było poznać czy rze­czy­wi­ście pod­świe­tla nam ten ele­ment, który wybraliśmy).