Doctrine Query Cache w Symfony

Dzisiaj o odkrywaniu Ameryki w konserwie. Właśnie takie miałem wrażenie, gdy dłubiąc w projekcie trafiłem na rozdział na temat cache w dokumentacji Doctrine. Wcześniej tam nie trafiłem, bo całe keszowanie robiłem w Symfony – wszak do pamięci podręcznej najlepiej wrzucać efekt końcowy. Dodatkowo w dokumentacji Symfony nie rzuciło mi się w oczy nic na temat cacheowania w Doctrine.

Nie zamierzam streszczać dokumentacji Doctrine. W kontekście tej notki ważne jest, że Doctrine może zapamiętywać w cache dwa rodzaje danych:

  • skompilowane zapytania
  • wyniki zapytań

Dzięki zapamiętywaniu zapytań czasochłonny proces parsowania zapytania i budowania docelowej SQL-ki jest wykonywany tylko raz, kolejne wywołania będą korzystać z danych zapamiętanych w pamięci podręcznej. Problem aktualności danych w cache nie istnieje ponieważ zmiana zapytania spowoduje automatyczne wygenerowanie nowych danych. Korzyści są niebagatelne, użycie proste jak konstrukcja cepa, a problemów nie ma. Dlatego zgodnie z dokumentacją i zdrowym rozsądkiem, pamięć podręczna zapytań powinna być zawsze włączona, nawet w środowisku testowym.

Włączenie cache dla Doctrine w projekcie Symfony jest banalne i sprowadza się do dodania króciutkiej metody w klasie config/ProjectConfiguration.class.php.

public function configureDoctrine(Doctrine_Manager $manager)
{
    $manager->setAttribute(Doctrine_Core::ATTR_QUERY_CACHE, new Doctrine_Cache_Apc());
}

Powyższy kod spowoduje, że skompilowane zapytania będą zapamiętywane w pamięci APC. Oprócz APC Doctrine może współpracować z serwerem memcached lub inną bazą danych (np. SQLite).

Cache można włączyć nie tylko na poziomie managera, równie dobrze można zrobić to w samym zapytaniu:

$q = Doctrine_Query::create()
    ->useQueryCache(new Doctrine_Cache_Apc());

Jak wspomniałem wcześniej, pamięć podręczna zapytań powinna być włączona zawsze. Na poziomie konkretnego zapytania więcej sensu ma włączanie zapamiętywania wyników:

$q = Doctrine_Query::create()
    ->useResultCache(new Doctrine_Cache_Apc());

Wymiatacze z TVN-u

Irytuje mnie postawa „Polak potrafi… spierdolić”, która często pojawia się w dyskusjach na temat różnorakich dokonań Polaków. Jednak gdy widzę coś takiego, jak „Wymiatacze” to zaczynam wierzyć, że coś jest na rzeczy.

W tym miejscu należy się wyjaśnienie. Dawno temu japoński teleturniej „Takeshi’s Castle” zauroczył mnie od pierwszych sekund. Ponieważ jeden obraz mówi więcej niż tysiąc słów posłużmy się dowodem rzeczowym numer jeden:

Czytaj dalej Wymiatacze z TVN-u

Bidet

Ta notka będzie stanowić dla mnie katharsis i to w sensie dosłownym, gdyż rzecz będzie o oczyszczeniu, jednak nie duszy. Z niejakim zażenowaniem muszę przyznać, że dopiero niedawno poznałem funkcję bidetu. Wcześniej przez długie lata sądziłem, że służy on do mycia nóg. Z błędu wyprowadziła mnie moja ukochana, a śmiechu było co niemiara.

Laughs have been had i w tym miejscu można by postawić kropkę, ale zacząłem zastanawiać się skąd wzięły się moje wyobrażenia o funkcji bidetu i jakim sposobem przez blisko trzydzieści lat brutalna prawda nie zdołała się przebić przez mury mej ignorancji. Uzupełniając wspomnienia domysłami wyszło mi, że po raz pierwszy z bidetem spotkałem się na zdjęciu, zapewne w jakimś katalogu wyposażenia łazienek, a miałem wtedy lat małonaście. Ponieważ z kontekstu w którym zdjęcie występowało nijak nie wynikała funkcja tego urządzenia, więc musiałem dorobić sobie własną interpretację na podstawie doświadczeń. Z doświadczenia zaś wiedziałem, że jeśli chce się dokonać szybkiej ablucji to niewygodnie jest zadzierać nogę do umywalki. Logicznym remedium na te niedogodności zdawało mi się urządzenie z bieżącą wodą wysokości mniej więcej klopa. Wypisz, wymaluj bidet. Logiczne, nie?

Teraz zastanawiam się ile jeszcze takich bidetów kryje mój leksykon.

W ramach post scriptum bidet z tubki.

Powiązanie nazwy interfejsu sieciowego z adresem MAC

Numerowanie interfejsów sieciowych w Linuksie jest dość tajemniczym procesem, przynajmniej dla mnie. Dość powiedzieć, że przeważnie system nadaje im inne numerki niż bym sobie życzył. Co więcej, jeśli mamy już w systemie interfejs eth0, nie ma gwarancji, że po dołożeniu drugiej karty sieciowej zostanie ona wykryta jako eth1. Na szczęście obecnie można w łatwy sposób przekonać system do swoich racji. W Debianie (i Ubuntu) należy skierować się ku plikowi /etc/udev/rules.d/70-persistent-net.rules. Właśnie w tym pliku system zapisuje powiązanie pomiędzy adresem MAC karty sieciowej a nazwą interfejsu. Przykładowa zawartość pliku:

# PCI device 0x10ec:0x8168 (r8169)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="70:71:bc:b1:93:94", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

# PCI device 0x10ec:0x8139 (8139too)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:02:44:70:1f:3e", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"

W tym konkretnym wypadku chciałem zamienić interfejsy kolejnością, tak aby eth0 był połączony z modemem, a gigabitowy eth1 z siecią lokalną. Żeby to osiągnąć wystarczyło zamienić cyferki w parametrze NAME:

# PCI device 0x10ec:0x8168 (r8169)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="70:71:bc:b1:93:94", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"

# PCI device 0x10ec:0x8139 (8139too)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:02:44:70:1f:3e", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

Korzystając z tego pliku można pójść dalej i nadać interfejsom bardziej czytelne nazwy np. eth-wan, eth-lan czy eth-intel.

Etykiety w YAML-u przyjacielem leniwego programisty

Sympatyczny wynalazek, jakim jest YAML, jest używany w Symfony niemal na każdym kroku. I bardzo lepiej, bo to bardzo przyjazny człowiekowi format zapisu. Dokumentacja Symfony na temat YAML-a jest jednak dość lakoniczna i nie opisuje wszystkich jego możliwości. Jednym z pominiętych zagadnień są na ten przykład etykiety, które pozwalają zredukować ilość wklepywanych danych.

Etykiety najbardziej przydają się przy definiowaniu danych statycznych aplikacji (fixtures). Załóżmy, że tworzymy katalog usług hostingowych. Usługę VPS można opisać chociażby tak:

  vps_hitme_openvz_128: &vps_hitme_openvz
    Company:            hitme
    name:               OpenVZ 128
    Virtualization:     openvz
    guaranteed_memory:  128
    burstable_memory:   256
    swap_memory:        0
    disk_space:         20
    uplink:             100
    downlink:           100
    bandwidth:          50
    ip_4_addresses:     1
    ip_6_mask:          64
    root_access:        true
    is_managed:         false
    url:                http://www.hitme.net.pl/openvz-details.php
    is_verified:        true
    is_active:          true

Kolejne pakiety różniące się od powyższego tylko nazwą, ilością dostępnej pamięci, powierzchnią dyskową i transferem można opisać o wiele krócej:

  vps_hitme_openvz_256:
    <<:                 *vps_hitme_openvz
    name:               OpenVZ 256
    guaranteed_memory:  256
    burstable_memory:   512
    disk_space:         20
    bandwidth:          100

Składnia i działanie etykiet w powyższym przykładzie są oczywiste, opiszę je jedynie dla porządku. Etykietę poprzedzamy znakiem &. Dane oznaczone etykietą włączamy używając operatora << jako klucza, którego wartością jest nazwa etykiety poprzedzona *. Spowoduje połączenie tablicy oznaczonej etykietą z bieżącą tablicą. Jeśli w obu tablicach występuje ten sam klucz to zostanie użyta wartość z bieżącej tablicy. Prawda, że przydatne?

Dane binarne w PHP

Przy pisaniu poprzedniej notki o base64_encode() przypomniało mi się inne zastosowanie base64, a mianowicie osadzanie danych binarnych w kodzie PHP. Poniższy kawałek kodu wypluwa przezroczysty obrazek w formacie GIF o rozmiarach 1 x 1 px. Jest to fragment skryptu, który zlicza otwarcia mailingu.

header('Content-Type: image/gif'); 
header('Expires: Wed, 11 Nov 1998 12:00:00 GMT'); 
header('Cache-Control: no-cache'); 
header('Cache-Control: must-revalidate'); 
die(base64_decode('R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA='));

Oczywiście równie dobrze plik można zapisać na dysku i wypluwać go przez file_get_contents() lub przekierować do niego przez wysłanie nagłówka Location, ale powyższe rozwiązanie ma tę zaletę, że plik jest osadzony w skrypcie. To może być istotne jeśli sprzedajemy skrypt zakodowany np. ionCubem i nie chcemy, żeby klient miał możliwość podmiany obrazka. Dodatkowo base64_decode() ma szanse być szybsze niż otwarcie i wczytanie zewnętrznego pliku, a od przekierowania przez nagłówek Location jest szybsze na bank.

Z drugiej strony osadzanie plików w skrypcie ma sens tylko dla niedużych plików, tak π razy oko do 2-3 KiB. Przy większych plikach wybrałbym file_get_contents(), ze względu na mniejszy rozmiar skryptu PHP i prawdopodobnie większą szybkość działania.

Z trzeciej strony, jeśli podstawowym celem jest szybkość działania, można pokusić się o jeszcze inne rozwiązanie.

define(EMPTY_GIF_IMAGE, "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\xff\xff\xff\x21\xf9\x04\x01\x0a\x00\x01\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b\x00");
header('Content-Type: image/gif'); 
header('Expires: Wed, 11 Nov 1998 12:00:00 GMT'); 
header('Cache-Control: no-cache'); 
header('Cache-Control: must-revalidate'); 
die(EMPTY_GIF_IMAGE);

Jeśli korzystamy z APC lub podobnego rozwiązania to po kompilacji skryptu stała EMPTY_GIF_IMAGE będzie zawierała nasz pliczek GIF w postaci gotowej do użycia, odpada konieczność konwersji z base64. Zastrzegam, że nie sprawdzałem tego w praktyce, ale zdziwiłbym się, gdyby nie było to najszybsze z opisywanych rozwiązań.

Krótsze skróty dzięki base64

Skróty wiadomości (ang. message digest, hash) to chleb powszedni w programowaniu. Przez funkcje md5(), sha1() itp. generowane są identyfikatory sesji, ciasteczka identyfikujące użytkownika, nazwy plików i tuzin innych rzeczy.

Skrót, jak sama nazwa wskazuje, powinien być krótki. Tymczasem w większości wypadków, z którymi się zetknąłem, skróty używane są w zapisie szesnastkowym, co oznacza, że 128 bitowy skrót MD5 jest zapisywany w 32 bajtach (czyli 256 bitach), a 160 bitowy SHA1 potrzebuje 40 znaków (256 bitów). To, delikatnie mówiąc, trąci rozrzutnością. Oszczędzić bajty można wykorzystując zapis base64, co pokazuje poniższy przykład:

var_dump(sha1('Ala ma kota')); 
var_dump(strtr(rtrim(base64_encode(sha1('Ala ma kota', true)), '='), '+/', '-_'));

Wynik:

string(40) "43fd70009a97a7d311c5644047ccc700f8d08a9d" 
string(27) "Q_1wAJqXp9MRxWRAR8zHAPjQip0"

Czytaj dalej Krótsze skróty dzięki base64

Ocalone tłumaczenia: Julian Tuwim

W nie tak odległej przeszłości studiowałem na warszawskiej anglistyce. Jednym z kursów, które wspominam najlepiej był kurs #2800 zatytułowany „Poezja polska w przekładzie” prowadzony przez dr Barry’ego Keane’a, którego z tego miejsca serdecznie pozdrawiam. Na kursie co tydzień braliśmy na warsztat jednego poetę; każdy uczestnik wybierał jeden lub kilka jego wierszy, szukał ich tłumaczeń na angielski, dokonywał krytycznej analizy tychże, a na koniec prezentował własne tłumaczenie. Na co dzień nie gustuję w poezji, ale tłumaczenie jej okazało się nadspodziewanie ciekawe. W każdym razie znalezienie kilku tłumaczeń jednego wiersza nie zawsze było łatwe, dlatego zakarbowałem sobie w pamięci, że jeśli kiedykolwiek dorobię się własnej strony to udostępnię na niej moje tłumaczenia co by ułatwić życie przyszłym pokoleniom anglistów, a być może umożliwić jakimś native speakerom obcowanie z polską poezją.

Na pierwszy ogień Julian Tuwim, nad jego wierszami pracowało mi się najlepiej. W ramach disklejmera wspomnę tylko, że tłumaczenie nie zawiera poprawek naniesionych w czasie zajęć ponieważ kartki na których notowałem uwagi dawno zaginęły. Bez dalszego przedłużenia, miłej lektury.

Karta z dziejów ludzkości A page from the history of humanity
Spotkali się w święto o piątej przed kinem
Miejscowa idiotka z tutejszym kretynem.

Tutejsza idiotko! – rzekł kretyn miejscowy –
Czy pragniesz pójść ze mną na film przebojowy?

Miejscowa kretynka odrzekła – Z ochotą,
Albowiem cię kocham, tutejszy idioto.

Więc kretyn miejscowy uśmiechnął się słodko
I poszedł do kina z tutejsza idiotką.

Na miłym macaniu spłynęła godzinka
I była szczęśliwa miejscowa kretynka.

Aż wreszcie szepnęła: – kretynie tutejszy!
Ten film, mam wrażenie, jest coraz nudniejszy.

Więc poszli na sznycel, na melbe, na winko,
Miejscowy idiota z tutejszą kretynką.

Następnie się zwarli w uścisku zmysłowym
Tutejsza idiotka z kretynem miejscowym.

W ten sposób dorobią się córki lub syna:
Idioty, idiotki, kretynki, kretyna.

By znowu się mogli spotykać przed kinem
Tutejsza idiotka z miejscowym kretynem.

At the cinema, on a bench therein
The local idiot met the resident cretin.

Local idiot! – says resident cretin –
Let’s go for a movie, I’m begging.

The local idiot replied – With pleasure,
As I love you beyond any measure.

The cretin gave the sweetest smile he got
And went for a movie with the local idiot.

To pleasant pawing they devoted some time
Which made the local idiot feel very fine.

Finally she whispered: – resident cretin!
The movie is getting more and more thin.

So out they went and some wine they bought,
The local cretin with the resident idiot.

Later in a sensual embrace they clashed in
The local idiot with the resident cretin.

The offspring is more likely than not:
A male cretin or a female idiot.

Which will allow in future for another meeting
Of the local idiot with the resident cretin.

Figielek A Frolic
Raz się komar z komarem przekomarzać zaczął
Mówiąc, że widział raki, co się winkiem raczą.

Cietrzew się zacietrzewił słysząc takie słowa,
Sęp zasępił się strasznie, osowiała sowa,

Kura dała drapaka, że aż się kurzyło,
Zając zajęczał smętnie, kurczę się skurczyło.

Kozioł fiknął koziołka, słoń się cały słaniał,
Baran się rozindyczył, a indyk zbaraniał.

Once a boa boasted to a choked chickadee
that he saw a sawfish which should be a bee.

A jellyfish felt jealous, a rat rattled at a hare,
Only a bear could bear it, so he didn’t care.

A clam clamoured at once it was an obvious lie
And a peacock peed itself, owl only knows why.

An ape gaped at a cow suffering from cowardice,
While an ant anticipated being antagonized.

Aster nie ceni wygody

Polecenie zapłaty to najwygodniejsza forma regulowania rachunków, zarówno dla klientów jak i usługodawców. Nic dziwnego, że firmy ją intensywnie promują. Niestety nie Aster. To znaczy owszem, promuje przez umieszczenie polecenia zapłaty na pierwszym miejscu listy sposobów płatności i odwołuje się do wygody klienta, ale nie oferuje żadnych wymiernych korzyści. Na ten przykład w Erze za każdą fakturę opłaconą poleceniem zapłaty dostaję 60 ichnich punktów Era Premia czyli więcej niż za samą wysokość rachunku. Dzięki temu mogę sobie uzbierać na pakiecik promocyjnych SMS-ów lub minut zanim poprzedni mi się skończy. Dzięki temu mogę mieć niższy abonament i płacić niższe rachunki – korzyść jest jak najbardziej wymierna.

Kolejne udogodnienie to faktury w formie elektronicznej. W tym wypadku korzyści dla firm (odpadają koszty druku i wysyłki, potrzeba mniej pracowników) są wyraźnie większe niż dla klientów, więc tym bardziej powinno im zależeć na przekonaniu klientów do tego rozwiązania. Przeważnie zależy, wspomniana wcześniej Era przyznaje klientom, którzy wybrali F@kturę, 60 minut na rozmowy – ponownie bardzo wymierna korzyść. Tymczasem Aster najwyraźniej nawet nie ma pomysłu jak zareklamować swoją eFakturę, skoro jako korzyści podaje rzeczy nie związane z eFakturą („odliczenia za Internet w Urzędzie Skarbowym”), oczywiste („brak rachunków papierowych”), czy też mnoży jedną zaletę („dostęp do faktur z każdego miejsca 24 h 7 dni w tygodniu ”, „szybki dostęp do informacji o wysokości rachunku ”, „dostęp po zalogowaniu w systemie WILGA”). Ponownie żadnych wymiernych korzyści dla klienta.

Oczywiście nie wiem ilu klientów Astera korzysta z polecenia zapłaty i eFaktury, więc nie mogę wykluczyć, że jest ich zdecydowana większość i firma nie ma potrzeby promować tych rozwiązań, jednak byłbym bardzo zdziwiony gdyby tak było. Z obserwacji wiem, że większość ludzi czuje się nieswojo gdy ktoś samodzielnie zabiera im pieniądze z konta, a do dokumentów, w których nie można zrobić dwóch dziurek i wpiąć do segregatora podchodzi z daleko posuniętą nieufnością.

Uważam, że Aster powinien bardziej zadbać o klientów, którzy ułatwiają mu „życie”. Ja ucieszyłbym się z kanału HD, filmu w VOD, przyspieszenia transmisji łącza internetowego albo dodatkowych megabajtów w internecie mobilnym, a możliwości jest dużo więcej. Tymczasem będę płacił rachunki poleceniem zapłaty i korzystał z eFaktur, bo to po prostu wygodne.

Czego mi brakuje w mBanku

mBank wprowadza kosmetyczne zmiany, ale istotnych zmian na razie nie widać.

Dzisiaj mBank pochwalił się nowym wyglądem strony logowania. Już za półtora tygodnia będzie ona wyglądała tak jak odświeżony niedawno serwis transakcyjny i reszta stron mBanku czyli tak:

mBank strona logowania

Proponowany wygląd podoba mi się bardziej niż obecny, a ujednolicenie wyglądu jest ze wszech miar dobrym pomysłem. Wszystko pięknie, ale… nie na takie zmiany czekam i, jak sądzę, nie ja jeden. Na stronie logowania spędzam dwie do trzech sekund, a póki działa jak trzeba jej wygląd niewiele mnie obchodzi.

O wiele bardziej przydatną funkcją byłby analizator wydatków, narzędzie, które na kolorowych wykresach pokazałoby na co rozchodzą się moje pieniądze. Obecnie takie analizy wykonuję z pomocą arkusza kalkulacyjnego, ale regularne wprowadzanie do niego danych jest cokolwiek upierdliwe. Bank ma wszystkie potrzebne dane, wystarczy je tylko wykorzystać. W kwietniu takie narzędzie swoim klientom zaoferował Meritum Bank i prawdę mówiąc liczyłem, że mBank, aspirujący do miana pioniera i lidera bankowości internetowej, zareaguje wprowadzeniem podobnej funkcjonalności. Póki co moje nadzieje okazały się płonne, ale jeszcze ich nie porzuciłem.

PS. Jestem świadom istnienia zewnętrznych serwisów oferujących analizę domowego budżetu, ale do żadnego z nich jakoś nie mogłem się przekonać, tym bardziej, że również w nich wprowadzanie danych nie jest w pełni automagiczne.