RSS
 

Domyślne uruchamianie Windowsa za pomocą GRUB boot loadera

13 kwi

Jakiś czas temu kupiłem nowego kompa i stary w schedzie przypadł mojej żonie. Mimo moich usilnych działań uświadamiających nadal przedkłada ona Windowsa ponad Linuxa więc jak tylko dostała laptopa w swoje władanie wierciła mi dziurę w brzuchu aby usunąć z dysku Ubuntu, a przynajmniej sprawić aby domyślnie uruchamiał się Windows 7.

Zmiana kolejności uruchamiania w GRUB boot loaderze jest dość prostą czynnością wielokrotnie opisywaną w necie. Należy jednak zwrócić uwagę na wersję GRUB-a, gdyż w przypadku np. najnowszego Ubuntu używany jest GRUB2, w którym zmianę kolejności bootowania robi się nieco inaczej.

Przede wszystkim należy wyedytować odpowiedni plik

sudo /etc/default/grub

i odnaleźć w nim opcję GRUB_DEFAULT domyślnie ustawioną na 0

GRUB_DEFAULT=0

Liczba odnosi się do pozycji w menu bootowania. Taka lista wpisów może wyglądać następująco:

Ubuntu, Linux 3.0.0-17-generic
Ubuntu, Linux 3.0.0-17-generic (recovery mode)
memory test (memtest86+)
memory test (memtest86+, serial console 115200)
Windows 7 (loader) (on /dev/sda1)

Tak więc aby ustawić Windowsa jako domyślnie uruchamiany system należało by opcji GRUB_DEFAULT przypisać 4

GRUB_DEFAULT=4

Na koniec niezbędnym jest jeszcze aktualizacja ustawień GRUBA2 poprzez wykonanie polecenia

sudo update-grub

To wystarcza by zmienić kolejność bootowania. Jednak jest mały problem. Polega na tym, że przy aktualizacji kernela do nowej wersji do menu dodawane są nowe linie i wpis dotyczący Windowsa przesuwa się na dalszą pozycję. Można oczywiście każdorazowo aktualizować ustawienia GRUB2-a jednak jest lepsze rozwiązanie.

W pliku /boot/grub/grub.cfg odszukujemy sekcję odpowiedzialną za uruchamianie Windowsa

menuentry "Windows 7 (loader) (on /dev/sda1)" --class windows --class os {
	insmod part_msdos
	insmod ntfs
	set root='(hd0,msdos1)'
	search --no-floppy --fs-uuid --set=root 24B2367EB2365510
	chainloader +1
}

i do opcji GRUB_DEFAULT przypisujemy tytuł wpisu zamiast jego numeru

GRUB_DEFAULT="Windows 7 (loader) (on /dev/sda1)"

W ten sposób co prawda pozycja wpisu w menu się nie zmieni, ale za to przy inicjalizacji domyślnie zaznaczona będzie pozycja w menu dotycząca Windowsa i jeżeli nie zareagujemy to po czasie określonym w opcji GRUB_TIMEOUT uruchomi się Windows.

Jak już pisałem aktualizację ustawień GRUB2 kończy polecenie sudo update-grub. Bez jego wykonania zmiany w ustawieniach nie zostaną uwzględnione.

 
No Comments

Posted in Linux

 

Facebook – dodawanie aplikacji do fanpage-a

12 mar

Facebook to dla developera wieczne utrapienie. Ciągłe zmiany w interfejsie, w API, dodawanie coraz to nowych funkcjonalności czy choćby permanentny redesign zmuszają do bezustannego poprawiania napisanych aplikacji. Pisząc aplikacje na facebook średnio raz na pół roku muszę być przygotowany na to, że połowę rzeczy, które nauczyłem się ostatnio implementować teraz będę musiał zrobić w zupełnie inny sposób. Czytanie tutoriali, czy wskazówek na blogach często nie ma sensu gdyż zamieszczone porady są już dawno nieaktualne. Łapię się na tym, że napisanie średnio rozbudowanej aplikacji zajmuje mniej czasu niż opublikowanie i zintegrowanie jej z facebookiem.

Dzisiaj klient zgłosił mi, że nie można dodać aplikacji do fanpage-a gdyż nigdzie nie ma przycisku „Add to my page”. Zawsze jak się wchodziło na stronę aplikacji była możliwość dodania jej do fanpage-a za pomocą jednego kliknięcia, a później ewentualnie skonfigurowanie jej tak aby wyświetlała się w zakładce, a teraz nie ma. I co? I zaczęło się rycie w dokumentacji i googlach.

W końcu znalazłem przepis na to jak dodać analogiczny przycisk do kodu samej aplikacji

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:fb="https://www.facebook.com/2008/fbml">
  <head>
    <title>My Add to Page Dialog Page</title>
  </head>
  <body>
    <div id='fb-root'></div>
    <script src='http://connect.facebook.net/en_US/all.js'></script>
    <p><a onclick='addToPage(); return false;'>Add to Page</a></p>
    <p id='msg'></p>
 
    <script> 
      FB.init({appId: "YOUR_APP_ID", status: true, cookie: true});
 
      function addToPage() {
 
        // calling the API ...
        var obj = {
          method: 'pagetab',
          redirect_uri: 'YOUR_URL',
        };
 
        FB.ui(obj);
      }
 
    </script>
  </body>
</html>

Irytuje mnie ta polityka Facebooka niemiłosiernie ponieważ, kiedy coś przestaje działać, albo wyglądać na facebooku klient ma pretensje do mnie. Ja z kolei nie mam ochoty poprawek i modyfikacji wynikających ze zmiany flow na facebooku robić w ramach gwarancji bo to często nie są sprawy 5 minutowe.

 
 

Ad ACTA – o prawie autorskim i prawach pokrewnych

26 sty
„Nie istnieje człowiek, sprawa, zjawisko, a nawet żadna rzecz, dopóty, dopóki w sposób swoisty nie zostały nazwane. Władzą jest więc moc swoistego nazywania ludzi, spraw, zjawisk i rzeczy, tak aby te określenia przyjęły się powszechnie. Władza nazywa, co jest dobre, a co złe, co jest białe, a co czarne, co jest ładne, a co brzydkie, bohaterskie lub zdradzieckie; co służy ludowi i państwu, a co lud i państwo rujnuje; co jest po lewej ręce, a co po prawej, co jest z przodu, a co z tyłu. Władza określa nawet, który bóg jest silny, a który słaby, co należy wywyższać, a co poniżać.”

Tymi słowy rozpoczął Zbigniew Nienacki jedną ze swoich powieści będącą wizją powstania państwa polskiego. Wątki i postaci przedstawione w książkach z serii „Dagome iudex” są wyimaginowane choć tło historyczne oraz miejsca mają odniesienie do rzeczywistości. Celne są też spostrzeżenia, jak i przemyślenia przemycane czytelnikom pod postacią wypowiedzi bohaterów, czy też cytatów z rzekomych dzieł, jak choćby przytoczony wyżej fragment rozdziału „O sztuce rządzenia ludźmi” z Księgi Grzmotów i Błyskawic.

Zatem nazwano nas piratami.

Kiedy pewien rolnik wymyślił kosę pomyślał sobie – Będę teraz rżnął żyto dwa razy szybciej od mojego sąsiada, który używa sierpa, a do tego ominą mnie bóle krzyża. Jakież musiało być jego rozczarowanie, kiedy sąsiad zrobił sobie takie samo narzędzie. Rolnik poszedł do sąsiada i powiedział.
- Coś mi się chyba należy za to, że ułatwiłem Ci pracę?
- Bóg zapłać – usłyszał w odpowiedzi.

A gdyby chroniły go prawa własności intelektualnej to rolnik mógłby odpowiedzieć:
- Boga zostaw w spokoju. Zapłać sam.

I albo sąsiad musiałby dalej kosić w kucki, albo wynalazca żyłby z licencji jak Microsoft, który zarabia więcej na patentach niż na bieżącej „produkcji”. Nie wiem jak szybko rozwinęło by się rolnictwo ale najbogatszym darmozjadem na ziemi byłby spadkobierca wynalazcy koła.

Życie bardów nie było łatwe ale odmieniło się wraz z wynalazkiem pozwalającym utrwalić dźwięk. Wystarczyło raz zaśpiewać i można to było sprzedać wielokrotnie. Co za pech!!! Że też ten rozwój technologiczny nie zatrzymał się na etapie płyt gramofonowych? Kto potrafił takowe kopiować? Taśmy magnetofonowe to nieszczęście ale mp3 i internet to już dla muzyków prawdziwa katastrofa – o wytwórniach już nie wspomnę. Nie dość, że trzeba znowu zacząć dawać koncerty to jeszcze stale wymyślać nowe utwory bo w końcu ile można słuchać „Somebody That I Used To Know” (może dla odmiany cover)?

Ale niektórzy muzycy tak nie chcą.

Czyż nie byłoby to wspaniałe gdybym – będąc kierowcą – mógł raz przejechać trasę autobusem, a potem autobus sam już by jeździł z zaprogramowanym wirtualnym kierowcą, a organizacje typu MPK (Miejskie Przedsiębiorstwo Komunikacyjne) zajmowało by się ochroną moich praw i kasowało każdego, kto chciałby się ze mną przejechać? Na szczęście nie jestem kierowcą autobusu bo moje marzenie nigdy by się nie spełniło. Jestem za to programistą więc kto wie?

Artyści wmawiają nam, że ściągając plik z internetu okradamy ich. Jeśli ja ukradnę komuś rower to ten ktoś nie ma roweru, ale jeśli ja wysłucham płyty w internecie to czy grupa, która ją nagrała traci ją? Krawiec też chciałby uszyć koszulę sprzedać ją i mieć ją nadal aby móc znowu ją sprzedać. Jeżeli technologia sprzyja firmie fonograficznej – umożliwia powielanie utworów w nieskończoność w celu jego wielokrotnej sprzedaży – to takie działanie każe nam się postrzegać jako moralne, a jeśli ta sama technologia pozwala zaoszczędzić pieniądze przeciętnego użytkownika to wtedy piętnuje się go mianem złodzieja.

- Jaskier – westchnął wiedźmin, robiąc się naprawdę senny. – Jesteś cynik, świntuch, kurwiarz i kłamca. I nic, uwierz mi, nic nie ma w tym skomplikowanego.

Andrzej Sapkowski w sadze o Wiedźminie – „Miecz przeznaczenia”.

Cztery lata poświęcił Michał Anioł freskom plafonowym w Kaplicy Sykstyńskiej. Zapewne papież Juliusz II nieźle za to zapłacił, co się Michałowi niewątpliwie należało bo arcydzieło wielkie popełnił. Ja bym w każdym razie nie malował sufitu kościoła przez cztery lata za grosze. Produkcje Hollywood też kosztują majątek, a aktorstwo wielu gwiazd zasługuje by je docenić, ale czy trzeba od razu gażę liczyć w milionach? Czy pensja w wysokości nauczyciela lub pielęgniarki nie jest godziwa? Dobre filmy zwracają się błyskawicznie a najlepsze zarabiają krocie pomimo tego, albo może wręcz dlatego, że są powszechnie dostępne. Najwięcej zarabia się nie na emisji samego filmu ale na wszelkiego rodzaju związanych z nim gadżetach. Jeśli jesteś matką lub ojcem, a Twoje dziecko zakochało się w McQueenie albo jego ulubionym bohaterem jest Buzz Astral to policz ile Cię w sumie kosztowała przyjemność obejrzenia z dzieckiem „The Cars” lub „Toystory”.

W „Księciu” Niccolò Machiavelli napisał „Ludzie tak są prości i tak naginają się do chwilowych konieczności, że ten, kto oszukuje, znajdzie zawsze takiego, który da się oszukać.” Wielu jest takich, którzy bazując na ludzkiej ciekawości, naiwności i poczuciu winy nie dość, że namówią klienta do kupienia często kiepskiego (wirtualnego) produktu, to jeszcze przekonają go do tego, że jest on wart ceny, którą zapłacił. Z drugiej strony także Machiavelli stwierdził „Kto pragnie oszukać, zawsze spotka takiego, który oszukać w żaden sposób nie pozwoli.”

W czasach tak zwanej rewolucji przemysłowej miliony harowały na garstkę kapitalistów, którzy bogacili się, aż pewnego dnia do władzy doszedł lud i nazwał ich burżujami. Nie pragnę rewolucji bo jak pokazuje historia jedna patologia zmieniła się w drugą. Teraźniejszość jednak udowadnia, że trzeba być czujnym bo to co nas dziś niepokoi jutro będzie przerażać. Co was przeraża w tym artykule? Piractwo?

Nie podoba mi się pomysł podpisania ACTA bo nie służy ochronie naturalnych praw do godziwego wynagrodzenia tylko do utrwalenia wynaturzonych praw do wyzyskiwania innych. Prawo do nazywania siebie autorem przysługuje każdemu, kto jest faktycznym twórcą lub pomysłodawcą danego dzieła, jednak prawa pokrewne w tym wszelkiego rodzaju koncesje, licencje, patenty itp. muszą ulec przedefiniowaniu gdyż w obecnej formie służą jedynie produktywności prawników ścigających się z komornikami w ściąganiu haraczy.

I kto tu jest piratem?

 
6 Comments

Posted in Priv

 

Lepszy var_dump czyli przyjemniejsze debugowanie PHP

12 sty

bigWeb/Debug/Dumper to narzędzie funkcjonalnie odpowiadające funkcji var_dump Jego przewagą jest sposób prezentacji danych, a także dodatkowe informacje ułatwiające debugowanie aplikacji.

Najpoważniejszą wadą Dumpera jest to, że jest on dość obciążający dla aplikacji gdyż uzyskanie informacji o pliku i linii, w której dump został wywołany wymaga każdorazowo rzucenia wyjątku. Dlatego też w wersji produkcyjnej Dumper powinien być wyłączony

UWAGA! Biblioteka zaprezentowana w przykładach wymaga min PHP 5.3 z uwagi na użycie przestrzeni nazw. Ponieważ jednak nie na wszystkich serwerach jest już PHP w wersji obsługującej przestrzenie nazw przygotowałem także wersję Dumpera nie wymagającą ich użycia. w takim przypadku wywołanie bigWeb\Debug\Dumper::factory(); należy zastąpić poprzez wywołanie bigWeb_Debug_Dumper::factory(); i analogicznie w przypadku innych klas. Wersja Dumpera dla PHP < 5.3 zawarta jest w archiwum zip bigWeb\Debug\Dumper do ściągnięcia.

Instalacja

include_once('./Debug.php');

Można też użyć autoloadera. Wszystkie klasy potrzebne do działania Dumpera zdefiniowane są w pliku „Debug.php”. Wyjątkiem jest FirePHP, który należy dodać osobno jeśli chcemy wyświetlać dane w konsoli javascript

include_once('./FirePHP.php');

Podstawowe użycie

dump('some data');

w wyniku otrzymamy:

TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 37 ET: 0 MU: 1.44 mb MPU: 1.67 mb
‘some data’
string(9) "some data"


Dumpa można wywołać z wieloma parametrami o różnych wartościach równocześnie.

$str = 'text';
$int = 7;
$arr = array('foo', 'bar');
$ob = new ArrayObject();
$bool = FALSE;
 
dump($str, $int, $arr, $ob, $bool);
TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 47 ET: 0.000904 MU: 1.47 mb MPU: 1.67 mb
$str
string(4) "text"
$int
int(7)
$arr
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
}
$ob
object(ArrayObject)#3 (1) {
  ["storage":"ArrayObject":private]=>
  array(0) {
  }
}
$bool
bool(false)


Ukrywanie komunikatów

Aby zapobiec wyświetlaniu jakichkolwiek komunikatów przez dumpera należy go wyłączyć.

bigWeb\Debug\Dumper::setEnabled(FALSE);
dump('it should not show');

Aby ponownie włączyć:

bigWeb\Debug\Dumper::setEnabled(TRUE);

Definiowanie alternatywnych logerów

Domyślnie dumper do zrzutu danych używa wbudowanej funkcji var_dump jednak klasa Dumpera jest zbudowana w oparciu o wzorzec projektowy Obserwator, gdzie obserwatorami są wyspecjalizowane klasy do logowania i prezentacji zrzucanych danych. Dzięki temu możemy wybierać sposób logowania informacji.

Możemy zapisywać dane w pliku – przydatne np. przy testowaniu przekierowań

$d = bigWeb\Debug\Dumper::factory();
$o = new bigWeb\Debug\Dumper\FileDump();

Koniecznym jest wskazanie katalogu w którym będą zapisywane logi. Katalog ten musi mieć oczywiście ustawione prawa do zapisu

$o->setDir(dirname(__FILE__));
$d->attach($o);
$d->notify('foo');

wynik zostanie zapisany w pliku /home/www/bigWeb/Debug/2012-01-12.log.php

TIME: 20:46:18 FILE: /home/zh/www/Debug/example.php LINE: 71 ET: 0.001143 MU: 1.55 mb MPU: 1.67 mb
----------| 'foo' |----------
foo
.----------------------------

Można też skorzystać z dobrodziejstw FireBuga – dodatku do Firefoxa i wyświetlać dane w konsoli javascript. W tym przypadku musimy najpierw załadować bibliotekę FirePHP będącej „pomostem” pomiędzy PHP i FireBugiem.

include_once('./FirePHP.php');
 
$d = bigWeb\Debug\Dumper::factory();
$d->attach(new bigWeb\Debug\Dumper\FireDump());
$d->notify('bar');

wynik pokarze nam się w konsoli FireBug-a

Wynik Dumpera w konsoli FireBug

UWAGA! Ponieważ informacje do FireBuga są przesyłane za pomocą nagłówków HTTP biblioteka FirePHP wymaga buforowania wyjścia (output bufering). Koniecznym jest zatem użycie funkcji ob_start() w przeciwnym razie można się spotkać z błędem „headers already sent error”

Łańcuch wywołań

Niekiedy chcemy wiedzieć jakie funkcje i metody zostały wywołane nim został wykonany kod w danym miejscu. Aby zobaczyć cały łańcuch wywołań należy ustawić flagę show_trace na TRUE.

function foo() {
    $d = bigWeb\Debug\Dumper::factory();
    $d->attach(new bigWeb\Debug\Dumper\VarDump());
    $d->setShowTrace(TRUE);
    $d->notify('Show chain requests');
}
 
function bar() {
    foo();
}
 
bar();
TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 97 ET: 0.005149 MU: 1.57 mb MPU: 1.67 mb
$d->notify(‘Show chain requests’);
string(19) "Show chain requests"

Array
(
    [0] => Array
        (
            [file] => /home/www/bigWeb/Debug/example.php
            [line] => 97
            [function] => notify
            [class] => bigWeb\Debug\Dumper
            [type] => ->
            [args] => Array
                (
                    [0] => Show chain requests
                )

    )

[1] => Array
    (
        [file] => /home/www/bigWeb/Debug/example.php
        [line] => 101
        [function] => foo
        [args] => Array
            (
            )

    )

[2] => Array
    (
        [file] => /home/www/bigWeb/Debug/example.php
        [line] => 104
        [function] => bar
        [args] => Array
            (
            )

    )

)

Praca na serwerze produkcyjnym

W zasadzie Dumper powinien być wyłączony na serwerze produkcyjnym bo obciąża aplikację, a ponadto może wyświetlać dane wrażliwe. Nie mniej w pewnych wyjątkowych okolicznościach możemy chcieć go użyć. Musimy wcześniej przewidzieć taką sytuację i dodatkowo skonfigurować przynajmniej jedną z instancji Dumpera ustawiając tak zwany secret_key

$d = bigWeb\Debug\Dumper::factory();
$d->attach(new bigWeb\Debug\Dumper\FireDump());
$d->setSecretKey('verysicretkey');

Aby te dane były widoczne należy wywołać url metodą GET z parametrem secret_key=1. W tym przypadku będzie to

example.php?verysicretkey=1

bigWeb\Debug\Dumper::setEnabled(FALSE);
$d->notify('Visible when set secret_key');

aby ponownie włączyć:

bigWeb\Debug\Dumper::setEnabled(TRUE);

Jeśli wywołamy url metodą GET z sekretnym kluczem, Dumper ustawia ciasteczko debugcookie o wartości wywiedzionej z secret_key. Od tej pory nie trzeba już dodawać do adresu żadnego specjalnego parametru. Jeśli jednak chcielibyśmy wyłączyć debugowanie należy wywołać url z secret_key=0

example.php?verysicretkey=0

Tworzenie funkcji pomocniczych

Tworzenie obiektu i dodawanie obserwatorów nie jest zbyt wygodnym rozwiązaniem. Narzędzie do debugowania powinno być extremalnie proste i szybkie w użyciu. Dlatego warto sobie zdefiniować funkcję pomocniczą – podobną do „debug();”

function dump_all() {
    $_args = func_get_args();
    static $d = null;
    if ( $d === null )
    {
        // Proszę zwrócić uwagę na wywołanie metody factory z parametrem $level = 3
        $d = bigWeb\Debug\Dumper::factory(3);
        $o = new bigWeb\Debug\Dumper\FileDump();
        $o->setDir(dirname(__FILE__));
        $d->attach($o);
        $d->attach(new bigWeb\Debug\Dumper\FireDump());
        $d->attach(new bigWeb\Debug\Dumper\VarDump());
 
    }
    call_user_func_array(array($d, 'notify'), $_args);
}
 
dump_all('foo bar');

wynik zostanie zapisany w pliku

TIME: 20:46:44 FILE: /home/zh/Praca/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb
----------| 'foo bar' |----------
foo bar
.--------------------------------

w konsoli FireBug-a

Wynik Dumpera w konsoli FireBug

oraz wyświetlony w przeglądarce:

TIME: 20:46:44 FILE: /home/www/bigWeb/Debug/example.php LINE: 155 ET: 0.006137 MU: 1.59 mb MPU: 1.67 mb
‘foo bar’
string(7) "foo bar"


UWAGA! Od wartości level zależy prawidłowe wskazanie linii oraz pliku, w którym wywołano dumpa. Domyślnie level = 1. Jeżeli metoda „notify” jest zagnieżdżona w funkcji pomocniczej to wartość level powinna być inkrementowana (level = 2). Jeśli dodatkowo metoda „notify” jest wywoływana za pośrednictwem funkcji „call_user_func_array” to należy ustawić level = 3

$d = bigWeb\Debug\Dumper::factory(1);
 
function d1($param) {
    $d = bigWeb\Debug\Dumper::factory(2);
    // ...
    $d->notify($param);
}
 
function d2($param) {
    $_args = func_get_args();
    $d = bigWeb\Debug\Dumper::factory(3);
    // ...
    call_user_func_array(array($d, 'notify'), $_args);
}

UWAGA! Jeśli masz już zdefiniowaną funkcję „dump” w swojej aplikacji to po dołączeniu kodu Dumpera otrzymasz wszystkomówiący wyjątek. Poinformuje Cię on, że musisz zdefiniować sobie funkcję pomocniczą o innej nazwie. Należy zakomentować kod wywołujący wyjątek lub też dołączyć kod Dumpera w sposób umożliwiający przechwycenie wyjątka i utworzyć funkcję pomocniczą np. o nazwie „d”.

try { include_once("Debug.php"); } catch (bigWeb\Debug\Exception $e) {
    function d() {
        $_args = func_get_args();
        static $d = null;
        if ( $d === null )
        {
            $d = bigWeb\Debug\Dumper::factory(3);
            $d->attach(new bigWeb\Debug\Dumper\VarDump());
 
        }
        call_user_func_array(array($d, "notify"), $_args);
    }
}

Napisana przeze mnie klasa nie zastąpi zaawansowanych i rozbudowanych narzędzi debugowania i profilowania aplikacji jednak jest prosta w użyciu nie związana stricte z żadnym frameworkiem, przez co łatwo ją zaadaptować zarówno do pracy z Zend Frameworkiem, Symfony, Kohaną czy jakimkolwiek innym – napisanym w PHP – skryptem. Biblioteka zwraca wyniki w postaci pokolorowanego kodu przy okazji pokazując czas wykonania oraz wielkość użytych zasobów. Zapomniany var_dump potrafi zmusić programistę do przeszukiwania plików projektu. W przypadku Dumpera nie ma takiego zagrożenia gdyż każdorazowo wskazuje on ścieżkę do pliku oraz nr linii, w której funkcja robiąca zrzut danych została wywołana.

Serdecznie zapraszam wszystkich do wypróbowania Dumpera oraz wszelkich uwag na temat wdrożeń i ewentualnych błędów.

 
5 Comments

Posted in PHP

 

Stan baterii w laptopie – linux

08 sty

Zaniepokoił mnie wskaźnik stanu naładowania baterii. Kilka godzin pracy na laptopie z podpiętym zasilaczem a 48% jak było tak było i nic nie chciało się ruszyć.

Polecenie:

cat /proc/acpi/battery/BAT1/info

Dało wynik:

present:                 yes
design capacity:         39960 mWh
last full capacity:      46320 mWh
battery technology:      rechargeable
design voltage:          11100 mV
design capacity warning: 420 mWh
design capacity low:     156 mWh
cycle count:              0
capacity granularity 1:  264 mWh
capacity granularity 2:  3780 mWh
model number:            PABAS024
serial number:           3658Q
battery type:            LION
OEM info:                LG

Czy to jest w ogóle możliwe aby projektowana pojemność baterii (design capacity) była mniejsza od ostatnio odnotowanej pełnej pojemności (last full capacity)? Mój laptop ma zaledwie dwa miesiące licząc od daty zakupu. Czy wartość pojemności projektowanej jest specjalnie zaniżana przez producentów, abym w przypadku sprawdzenia parametrów baterii – dajmy na to po roku użytkowania – czuł się bardziej komfortowo z powodu teoretycznie mniejszego zużycia baterii? A może te parametry są po prostu źle odczytywane?

Tak czy inaczej nie zmienia to faktu, że pomimo podłączonego zasilacza moja bateria zwyczajnie się nie ładowała. Pod Windowsem dostałem przynajmniej czytelny komunikat, natomiast widżet w KDE zwyczajnie mnie okłamywał. Jak już jesteśmy przy KDE to przy okazji zakupu nowego laptopa zdecydowałem się powrócić do KDE po zawodzie jaki mi sprawiło Unity. Znajomy zapewniał mnie, że Plasma została już dopracowana – NIE ZOSTAŁA.

Wracając jednak do baterii. Wykonałem kilkakrotnie polecenie:

cat /proc/acpi/battery/BAT1/state

Dało wynik:

present:                 yes
capacity state:          ok
charging state:          charged
present rate:            0 mW
remaining capacity:      22230 mWh
present voltage:         10924 mV

Wartość remaining capacity nie ulegała zmianie. Postanowiłem rozładować całkowicie baterię, a później spróbować ją naładować od zera. Czekając na rozładowanie baterii znalazłem i trochę zmodyfikowałem skrypcik w bash-u pokazujący stan baterii – (może się komuś przyda).

#!/bin/sh
 
CUR=`cat /proc/acpi/battery/BAT*/state | grep remaining | awk {'print $3'}`
STATE=`cat /proc/acpi/battery/BAT*/state | grep charging | awk {'print $3'}` 
MAX=`cat /proc/acpi/battery/BAT*/info | grep full | awk {'print $4'}`
DESIGN=`cat /proc/acpi/battery/BAT*/info | grep 'design capacity:' | awk {'print $3'}`
PRC=0
 
REAL_CAP=$(($MAX*100/$DESIGN))
PRC=$(($CUR*100/$MAX))
if [ $PRC -gt 100 ]; then
    PRC=100
fi 
 
echo 'State : '$STATE
echo 'Design Battery Capacity : '$DESIGN' (100%)'
echo 'Real Battery Capacity : '$MAX' ('$REAL_CAP'%)'
echo 'Current Battery capacity : '$CUR' ('$PRC'%)'

Po całkowitym rozładowaniu, przy wyłączonym komputerze podłączyłem zasilacz. Dioda zaczęła pokazywać, że bateria się ładuje. I rzeczywiście bateria naładowała mi się całkowicie. Nie jestem sprzętowcem i nie czytam fachowej prasy dlatego nie wiem czy to prawidłowe działanie – jakieś zabezpieczenie przed przedwczesnym zużyciem baterii czy też objaw uszkodzenia sprzętu? W innych laptopach, z którymi się spotkałem mogłem w każdej chwili doładować baterię.

 
1 Comment

Posted in Priv

 

MySQL-owe widoki w Django

28 paź

Niniejszy artykuł traktuje o widokach w bazie danych (konkretnie w MySQL) i możliwości tworzenia modeli do tychże widoków z użyciem ORM-a framewokra Django. Zwracam na to uwagę aby nie pomylić – pomimo zbieżności nazw – widoków SQL-owych z widokami (views.py) Django.

Czym są właściwie widoki w relacyjnej bazie danych i po co się je tworzy wyjaśnia – skądinąd świetnie napisany rozdział pod tytułem „Widoki” w przygotowanym przez Heliona kursie MySQL.

W jednym z projektów, nad którymi pracuję stanąłem przed potrzebą stworzenia widoku w MySQL-u. Postanowiłem więc zdefiniować taki widok, stworzyć do niego model w Django i posługiwać się jak dowolnym innym modelem z tym, że tylko do odczytu. ORM w Django jest jednym z najbardziej rozbudowanych i zaawansowanych narzędzi tego typu mimo to ma swoje ograniczenia dlatego też nie byłem pewien czy uda mi się zrealizować to co sobie założyłem. Okazało się to możliwe aczkolwiek dopiero od wersji 1.3 frameworka Django.

Poniżej prezentuję jak zdefiniować prawidłowo model dla widoku. Na potrzeby tego artykułu stworzyłem możliwie proste przykłady aby pokazać mechanizm. Skonstruowany przeze mnie widok był daleko bardziej rozbudowany.

Załużmy, że mamy nowy projekt django a w nim aplikację do prezentacji newsów.

news/models.py

from django.db import models
from django.contrib.auth.models import User
 
class Article(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True, max_length=100)
    text = models.TextField()
    added_by = models.ForeignKey(User)
    added_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)

news/admin.py

from news.models Article
from django.contrib import admin
 
class ArticleAdmin(admin.ModelAdmin):
    list_filter = ['is_active']
    list_display = ('name', 'added_by', 'added_on', 'updated_on', 'is_active')
    prepopulated_fields = {'slug': ('name',)}
    search_fields = ('name', )
 
admin.site.register(Article, ArticleAdmin)

Z pewnych powodów potrzebujemy widoku, który prezentuje się następująco:

DROP VIEW IF EXISTS `users_newsview`;
CREATE VIEW `users_newsview` AS
    SELECT 0 AS id, n.id AS news_id, n.name AS news_name, n.added_by_id AS user_id, u.username
    FROM news_article AS n
    INNER JOIN auth_user AS u ON u.id = n.added_by_id;

Tworzymy do niego model, który będzie tylko do odczytu.

users/models.py

from django.db import models
from news.models import Article
from django.contrib.auth.models import User
 
class NewsView(models.Model):
 
    class Meta:
        managed = False
 
    news = models.ForeignKey(Article, null=True, blank=True, on_delete=models.DO_NOTHING)
    news_name = models.CharField(max_length=255, null=True, blank=True)
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.DO_NOTHING)
    username = models.CharField(max_length=255, null=True, blank=True)

O tym, że obiekty modelu „NewsViews” są niemodyfikowalne decyduje Zdefniowany w podklasie „Meta” parametr „managed = False”. Innym bardzo istotnym elementem jest dodanie do definicji kluczy obcych „on_delete=models.DO_NOTHING”. Django w przypadku kluczy obcych domyślnie emuluje zachowanie „on delete cascade”. Możliwość zmiany tego zachowania Django wspiera dopiero od wersji 1.3. Jeśli nie zmienimy sposobu w jaki Django ma postępować z elementami powiązanymi w trakcie usuwania danego obiektu to przy próby usunięcia newsa, albo usera zostanie zgłoszony błąd gdyż próba usunięcia także rekordu widoku z oczywistych względów się niepowiedzie.

W starszej wersji frameworka Django też można stworzyć model do widoku, ale trzeba w nim zrezygnować z tworzenia relacji do innych obiektów i zamiast pól „news” i „user” zdefiniować pola typu integer „news_id” oraz „user_id”. Jak się domyślacie rozwiązanie to jest o wiele mniej wygodne.

 
2 Comments

Posted in Python

 

PHP – operacje na bitach w praktyce

20 wrz

Na początku mojej nauki PHP kupiłem sobie książkę „PHP4 Aplikacje” (Tobiasa Ratschiller i Till Gerken – Wydawnictwo Robomatic). Zawarta w tej lekturze tematyka była wtedy dla mnie zbyt zaawansowana i potem wielokrotnie wracałem do tej pozycji stopniowo dojrzewając do poruszanych w niej tematów. Najdłużej wzbraniałem się przed zgłębieniem wiedzy dotyczącej operacji na bitach. Dzisiaj nie wiem właściwie dlaczego bo zagadnienie jest całkiem proste, a rozwiązania oparte o system binarny mają wiele zalet.

Najpowszechniej chyba spotykanym przypadkiem stosowania wartości bitowych są wszelkiego rodzaju systemy uprawnień. Każdy chyba programista PHP zaprzyjaźnił się z dyrektywami

ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);

Druga z wymienionych dyrektyw ma też odpowiadającą jej funkcję „error_reporting”, która jako argument przyjmuje pozom raportowania błędów. Poziom ten można przekazać w postaci maski bitowej złożonej ze stałych odzwierciedlających wartości przypisane poszczególnym rodzajom błędów PHP.

Wartość bitowe stałych nie są jak widać w formacie binarnym tylko dziesiętnym.

stałazapis w formacie dwójkowym (binarnym)zapis w formacie dziesiętnym
E_ERROR000000011
E_WARNING000000102
E_PARSE000001004
E_NOTICE000010008

Do przeliczania wartości binarnych na dziesiętne służy w PHP funkcja bindec, a z dziesiętnych na binarne decbin.

I tak wywołanie …

error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

spowoduje że wyświetlane będą wszystkie błędy czasu wykonania, ostrzeżenia, błędy parsowania oraz uwagi.

Z kolei użycie takiej maski …

error_reporting(E_ALL &~ E_NOTICE);

pozwoli na ukrycie wszystkich mało ważnych uwag. Natomiast wszystkie pozostałe błędy i bardziej ważne ostrzeżenia będą dalej raportowane.

W celu zrozumienia wyżej zaprezentowanych operacji niezbędnym będzie poznanie operatorów logicznych. Ambitnych z kolei odsyłam do Algebry Boole’a, choć nie zachęcam zbyt mocno.

Czasami zbyt gruntowny wykład teoretyczny utrudnia zrozumienie prostych spraw, które podawane w małych porcjach powoli poszerzają horyzonty. Czytałem kilka prac poświęconych operacjom na bitach, w których była cała masa operacji na 0 (zerach) i 1 (jedynkach), wiele tabel, wzorów, równań. Wiele z nich wspominało o prawach De Morgana czy postulatach Huntigtona, ale mało który materiał traktował o tym jak tego używać. Dopiero niedawno kolega podesłał mi link do artykułu, który podchodzi do tematu z praktycznej strony.

Nospor w jednym z wpisów na swoim blogu pt. opcje dwuwartościowe prezentuje studium przypadku użycia operacji bitowych. Proponuje zastąpienie kilku flag – czyli dwustanowych pól przyjmujących wartość logiczną TRUE lub FALSE – w tabeli w bazie danych, jednym polem przechowującym wartość bitową.

Polega to konkretnie na tym, że zamiast mieć w tabeli trzy pola i trzy indeksy:

CREATE TABLE `offer` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(32) NOT NULL,
  `is_active` tinyint UNSIGNED NOT NULL DEFAULT 0,
  `is_promotion` tinyint UNSIGNED NOT NULL DEFAULT 0,
  `is_sale` tinyint UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY  (`id`),
  KEY `is_active` (`is_active`),
  KEY `is_promotion` (`is_promotion`),
  KEY `is_sale` (`is_sale`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Można je zastąpić jednym polem i jednym indeksem.

CREATE TABLE `offer` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(32) NOT NULL,
  `options` tinyint UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY  (`id`),
  KEY `options` (`options`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Dla trzech, niezależnych od siebie opcji powyższe rozwiązanie nie jest zbyt wygodne, ale dla potrzeb edukacyjnych wystarczy. Niżej pokazuję bardziej adekwatny przypadek użycia, tymczasem wyjaśniam że aby móc zapisać w jednym polu wartości kilku opcji trzeba najpierw każdej opcji przypasować bit.

// Normalnie staram się nie nadużywać stałych 
// jednak tutaj zrobiłem to dla jasności przykładów
define('OPT_NONE', 0);_
define('OPT_IS_ACTIVE', 1);
define('OPT_IS_PROMOTION', 2);
define('OPT_IS_SALE', 4);

Przy okazji chciałbym wspomnieć że poniższe zapisy są tożsame.

11 << 0bindec(’00000001′)
21 << 1bindec(’00000010′)
41 << 2bindec(’00000100′)

Wiedząc jaki bit oznacza jaką opcję możemy zapisać tę informację w bazie danych. W tym celu trzeba zsumować poszczególne opcje przy pomocy logicznego operatora OR „|”.

// Wszystkie opcje
$options = (OPT_IS_ACTIVE | OPT_IS_PROMOTION | OPT_IS_SALE);
// otrzymana wartość to 7
 
// Oferta jest aktywna i w wyprzedaży, ale nie w promocji
$options = (OPT_IS_ACTIVE | OPT_IS_SALE);
// otrzymana wartość to 5
 
// Oferta jest w promocji, ale nie jest aktywna ani w wyprzedaży
$options = OPT_IS_PROMOTION;
// otrzymana wartość to 2

Kiedy już przypiszemy ofercie jakieś opcje chcielibyśmy sprawdzić czy takową posiada.

// Oferta jest aktywna i w wyprzedaży, ale nie w promocji
$options = (OPT_IS_ACTIVE | OPT_IS_SALE);
 
// Upewniamy się czy oferta jest aktywna
($options & OPT_IS_ACTIVE) > 0 ? 'yes' : 'no';
// otrzymany wynik yes
 
// Sprawdzamy czy jest w promocji
($options & OPT_IS_PROMOTION) > 0 ? 'yes' : 'no';
// otrzymany wynik no
 
// Sprawdzamy czy jest aktywna i w wyprzedaży
($options & (OPT_IS_ACTIVE | OPT_IS_SALE)) == ((OPT_IS_ACTIVE | OPT_IS_SALE)) ? 'yes' : 'no';
// otrzymany wynik yes
 
// Sprawdzamy czy jest w promocji i/lub w wyprzedaży
($options & (OPT_IS_PROMOTION | OPT_IS_SALE)) > 0 ? 'yes' : 'no';
// otrzymany wynik yes
 
// Sprawdzamy czy jest w promocji albo w wyprzedaży
($options & (OPT_IS_PROMOTION | OPT_IS_SALE)) != (OPT_IS_PROMOTION | OPT_IS_SALE) ? 'yes' : 'no';
// otrzymany wynik yes

Proszę zwrócić uwagę na zastosowane nawiasy. Operatory logiczne mają priorytet niższy od operatorów arytmetycznych, a także od operatorów porównania więc jeśli wykonamy test (2 | 4 == 6) to z pewnością otrzymamy inny wynik niż jeśli zastosujemy następujący zapis ((2 | 4) == 6)

Opcje możemy modyfikować

// Oferta jest aktywna i w wyprzedaży, ale nie w promocji
$options = (OPT_IS_ACTIVE | OPT_IS_SALE);
// 5
 
// Dodajemy opcję w promocji
$options |= OPT_IS_PROMOTION;
// 7
 
// Usuwamy opcję w wyprzedaży
$options &= ~OPT_IS_SALE;
// 3
 
// Usuwamy też opcję jest aktywna
$options ^= OPT_IS_ACTIVE;
// 2
 
// I dodajemy ją spowrotem
$options ^= OPT_IS_ACTIVE;
// 3

Jak widać w ostatnim przykładzie operator ^ jest przełącznikiem, który usuwa bit jeśli jest ustawiony i ustawia jeśli nie jest ustawiony.

Przykład opcji – zapożyczony zresztą od Nospora – nie prezentuje pełnego potencjału bitów, które nie bez powodu są często używane przy konstruowaniu wszelkiego rodzaju systemów uprawnień. Oprócz oszczędności miejsca za pomocą szablonów bitowych można w łatwy sposób zaimplementować dziedziczenie uprawnień.

Stwórzmy kilka stref dostępu, a następnie grupy użytkowników, o różnych poziomach uprawnień pozwalających na dostęp do poszczególnych stref.

strefy dostępu

  • Strefa dla wszystkich – dostęp do niej powinni mieć wszyscy użytkownicy.
  • Strefa dla użytkowników uwierzytelnionych. Zalogowani użytkownicy powinni mieć dostęp do tego co użytkownicy anonimowi oraz do kilku innych funkcjonalności
  • Strefa dla moderatorów – moderatorzy mogą z założenia wszystko to co użytkownicy zalogowani, ale mają też funkcje edycyjne.
  • Strefa dla sponsorów – ich pole obejmuje zakres aktywności użytkowników zalogowanych oraz częściowo pokrywa się ze strefą moderatora. Nie mogą jednak edytować treści, za to mają dostęp do raportów, które zwykły moderator nie ma prawa widzieć
  • Administrator jak to zwykle bywa może wszystko

Przypiszmy każdej ze stref bit.

$for_logged = 1;
$for_moderators = 2;
$for_sponsors = 4;
$for_administrators = 8;

Następnie poszczególnym grupom użytkowników ustawmy taki szablon bitowy, który pozwoli im na dostęp do określonych stref. Przy pomocy szablonów bitów stosunkowo łatwo jest zdefiniować hierarchię grup użytkowników pozwalającą zrealizować założenie dziedziczenia uprawnień.

$logged_user = 1;
$moderator = $logged_user | 2; // 3
$sponsor = $logged_user | 4; // 5
$admin = $moderator | $sponsor | 8; // 15

Przetestujmy!

$bob = $sponsor; // 5
 
// Czy Bob ma dostęp do strefy zalogowanych użytkowników?
($for_logged & $bob) > 0 ? 'yes' : 'no';
// wynikiem jest yes
 
// Czy Bob ma dostęp do strefy moderatora?
($for_moderators & $bob) > 0 ? 'yes' : 'no';
// wynikiem jest no
 
// Czy Bob ma dostęp do strefy sponsorów?
($for_sponsors & $bob) > 0 ? 'yes' : 'no';
// wynikiem jest yes
 
// Czy Bob ma dostęp do strefy administratorów?
($for_administrators & $bob) > 0 ? 'yes' : 'no';
// wynikiem jest no

Operatory bitowe wyglądają tak samo w PHP, Pythonie czy MySQL-u. Można zapisać strefy, grupy i użytkowników bazie danych i większość operacji wykonać za pomocą zapytań sql-owych.

CREATE TABLE `zones` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 255 ) NOT NULL ,
`level` TINYINT NOT NULL DEFAULT '0'
) ENGINE = MYISAM ;
 
INSERT INTO `zones` (`id`, `name`, `level`) VALUES 
(1, 'for_logged', '1'), (2, 'for_moderators', '2'), 
(3, 'for_sponsors', '4'), (4, 'for_administrators', '8');
 
CREATE TABLE `groups` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 255 ) NOT NULL ,
`perms` TINYINT NOT NULL DEFAULT '0'
) ENGINE = MYISAM ;
 
INSERT INTO `groups` (`id`, `name`, `value`) VALUES 
(1, 'logged_user', '1'), (2, 'moderator', '3'), 
(3, 'sponsor', '5'), (4, 'administrator', '15');
 
CREATE TABLE `users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 255 ) NOT NULL ,
`groups_id` INT UNSIGNED NOT NULL
) ENGINE = MYISAM ;
 
INSERT INTO `users` (`id`, `username`, `groups_id`) VALUES 
('1', 'Frank', '1'), ('2', 'Charlie', '2'), ('3', 'Bob', '3'), 
('4', 'Jon', '4'), ('5', 'Mary', '1');

Poniżej użyłem podzapytań w celu pobrania listy użytkowników, którzy mają dostęp do strefy dla użytkowników zalogowanych, a następnie dla moderatorów.

SELECT username FROM `users` AS u INNER JOIN groups AS g ON u.groups_id = g.id 
WHERE g.perms & (SELECT z.level FROM zones AS z WHERE z.name = 'for_logged');
// wynikiem jest lista: Frank, Mary, Charlie, Bob, Jon
 
SELECT username FROM `users` AS u INNER JOIN groups AS g ON u.groups_id = g.id 
WHERE g.perms & (SELECT z.level FROM zones AS z WHERE z.name = 'for_moderators');
// wynikiem jest lista: Charlie, Jon

Na zakończenie chciałbym zaprezentować mały testowy skrypt. Jeśli nie chce Ci się analizować kodu, skopiuj go, zapisz w pliku i uruchom, a wszystko stanie się jasne.

/**
 * Rodzaje powiadomień
 */
$notification_methods = array(
    'email' => 1,
    'internal_message' => 2,
    'notification' => 4,
    'wall' => 8
);
 
 
$nm = $notification_methods;
 
/**
 * Rodzaje zdarzeń
 *
 * Do każdego rodzaju zdarzeń przypisane są dopuszczalne rodzaje powiadomień
 * np. o prywatnej wiadomości można powiadomić mailem lub za pośrednictwem
 * wewnętrznej wiadomości, ale nie wolno wyświetlić tej informacji na ścianie
 */ 
$event_types = array(
    'newsletter'        => $nm['email'],
    'invite_to_friends' => $nm['internal_message'] | $nm['notification'],
    'priv_message'      => $nm['email'] | $nm['internal_message'],
    'image_comment'     => $nm['internal_message'] | $nm['notification'] | $nm['wall']
);
 
 
print '<form action="" method="post">';
foreach ($event_types as $event_name => $avaliable_noti_methods) {
    echo '<p>';
    echo '<strong>'.$event_name.'</strong><br>';
    foreach ($notification_methods as $noti_name => $noti_val) {
        if ($avaliable_noti_methods & $noti_val) {
            $checked = (isset($_POST[$event_name.'-'.$noti_name]) ? 'checked="checked"' : '');
            echo '<input type="checkbox" name="'.$event_name.'-'.$noti_name.'[]" id="id_'.$event_name.'-'.$noti_name.'" '.$checked.'>';
            echo '<label for="id_'.$event_name.'-'.$noti_name.'">'.$noti_name.'</label><br>';
        }
    }
    echo '</p>';
}
echo '<p><input type="submit" name="ok" value="OK"></p>';
echo '</form>';
 
if (isset($_POST['ok'])) {
 
    $user_notification_settings = array_combine(array_keys($event_types), array_fill(0,4,0));
    foreach (array_keys($_POST) as $key) {
        $values = explode("-", $key);
        if (count($values) != 2) continue;
        list($event_name, $noti_name) = $values;
 
        if (!isset($user_notification_settings[$event_name])) {
            $user_notification_settings[$event_name] = $notification_methods[$noti_name];
        } else {
            $user_notification_settings[$event_name] |= $notification_methods[$noti_name];
        }
    }
 
    printf('<strong>Ustawienia wybrane przez użytkownika</strong>%s', print_r($user_notification_settings, true));
 
    print '<strong>Podsumowanie</strong><br>';
    foreach ($user_notification_settings as $event_name => $avaliable_noti_methods) {
        foreach ($notification_methods as $noti_name => $noti_val) {
            if ($avaliable_noti_methods & $noti_val) {
                print $event_name.' - tak - '.$noti_name.'<br>';
            } else {
                print $event_name.' - NIE - '.$noti_name.'<br>';
            }
        }
    }
}
 
2 Comments

Posted in PHP

 

Nice url – czyli przyjazne linki

31 sie

Jakiś czas temu pisałem o routingu w Kohana 3.1, w którym to wpisie stwierdziłem, że nie działają mi nazwane podwzorce (named subpattern) pomimo tego że mam wersję PHP wyższą niż minimalna wymagana 5.2.2. Trafiłem jednak na wpis nospora „Ładne url’e (nice url)” i postanowiłem mimo wszystko powrócić do tematu routingu i powalczyć z tymi wyrażeniami regularnymi.

Może to kwestia konfiguracji komputera – gdyż eksperyment przeprowadzałem na innym kompie niż poprzednio ale tym razem okazało się, że jednak da się w PHP nadawać nazwy wycinkom wzorca.

<?php
// Przykładowe urle
$urls = array(
    '/pl/news/list/1/',
    '/news/list/1/',
    '/news/list/',
);
// Ścieżka która pasuje do powyższych urli - parametry lang i page są opcjonalne
$route = '%^/((?P<lang>\w{2})/)?(?P<controler>\w+)/(?P<method>\w+)/((?P<page>\d+)/)?$%';
 
// Definicja wartości domyślnych parametrów opcjonalnych
$defaults = array('lang' => 'pl', 'page' => 1);
 
// Odczytanie ze ścieżki nazw parametrów
preg_match_all('/P\<(\w+)\>/', $route, $matches, PREG_PATTERN_ORDER);
$params = $matches[1];
 
foreach ($urls as $url) {
    // Założyłem w przykładzie, że wszystkie urle pasują do ścieżki 
    // więc nie sprawdzam czy tak jest
    preg_match($route, $url, $matches);
    $vars = array();
    foreach ($params as $param) {
        // Oczytanie z urla wartości parametrów lub nadanie wartości domyślnych
        $vars[$param] = (isset($matches[$param]) && !empty($matches[$param])) 
            ? $matches[$param] : $defaults[$param];
    }
    print $url."<br />"; print_r($vars);
}
?>

Wynikiem wywołania powyższego kodu jest

/pl/news/list/1/

Array
(
    [lang] => pl
    [controler] => news
    [method] => list
    [page] => 1
)
/news/list/1/
Array
(
    [lang] => pl
    [controler] => news
    [method] => list
    [page] => 1
)
/news/list/
Array
(
    [lang] => pl
    [controler] => news
    [method] => list
    [page] => 1
)

Wyżej przytoczony przykład to tylko proof of concept, którego stworzenie nie zajęło mi więcej czasu niż wpisanie tych paru słów komentarza. Daje on jednak pojęcie jak łatwe i przyjemne jest stworzenie routingu w oparciu o nowe możliwości wyrażeń regularnych (nowe w PHP :) ).

 
No Comments

Posted in PHP

 

Kohana 3 mod_rewrite i błąd „No input file specified”

02 lip

Stawiałem już projekty oparte na frameworku Kohana 3 na różnych serwerach. Jak dotąd zawsze działał mi plik .htaccess o treści:

# Turn on URL rewriting
RewriteEngine On
 
# Installation directory
RewriteBase /
 
# Protect application and system files from being viewed
RewriteRule ^(?:application|modules|system)\b - [F,L]
 
# Allow any files or directories that exist to be displayed directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
 
# Rewrite all other URLs to index.php/URL
RewriteRule .* index.php/$0 [PT]

(No ok mój plik htaccess jest bardziej robudowany, ale dla przejrzystości problemu podaję wersję minimalną – standardzik.)

Dzisiaj jednak szybkie i przyjemne przerzucenie projektu na serwer docelowy zostało brutalnie zburzone przez niemiły komunikat „No input file specified”, który pokazywał mi się przy próbie przejścia na dowolną podstronę.

Początkowo próbowałem użyć dyrektywy .htaccess-a

Options -MultiViews

Przy jakimś projekcie to mi kiedyś pomogło o ile dobrze kojarzę, ale tym razem nie.

Problematyczna okazała się linijka:

RewriteRule .* index.php/$0 [PT]

, którą na wszelkich forach, blogach itd. proponowano zastąpić na kilka różnych sposobów

RewriteRule .* index.php?$0 [PT,L,QSA]

albo

RewriteRule .* index.php [L]

jednemu podobno zadziałało coś takiego

RewriteRule .* index.php?kohana_uri=$0 [PT,L,QSA]

Mnie zadziałało dowolne z powyższych rozwiązań, ale tylko połowicznie. To znaczy – komunikat błędu zniknął, ale za to bez względu na wybraną podstronę zawsze pokazywała mi się strona główna. Nienawidzę takich zagadek.

Przeanalizowałem zawartość tablicy $_SERVER na moim serwerze testowym oraz produkcyjnym i wyszło mi, że na tym drugim brakuje PATH_INFO. Widać routing Kohany (przynajmniej w wersji 3.0) korzysta z tej wartości i bez niej zwyczajnie przestaje działać.

Ostatecznym remedium na mój kłopot okazało się prostackie obejście

$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'];

Ja osobiście dodałem to w bootstrapie.

 
2 Comments

Posted in PHP

 

Routing w Kohana 3.1

19 cze

Z frameworkiem Kohana pracuję już kilka lata. O ile Kohana w wersji 2 była przyjazna i w miarę dobrze udokumentowana o tyle 3-cie wydanie tego frameworka jest już mniej przyjazne. Nie zrozumcie mnie źle – ogólnie zmiany w architekturze i implementacji oceniam pozytywnie, ale ogromne braki w dokumentacji i ciągłe zmiany w kodzie mają zdecydowanie negatywny wpływ na przyjemność pracy z tym oprogramowaniem.

Zabieram się właśnie za nowy projekt. Jest niewielki więc postanowiłem wypróbować Kohanę w najnowszej „stabilnej” wersji 3.1. Utworzyłem katalog dla projektu, przekopiowałem do niego pliki frameworka, utworzyłem plik hteaccess według wzoru i odpaliłem w przeglądarce adres.


http://localhost/project/

Uruchomił mi się instalator, który poinformował mnie, że muszę nadać prawa do zapisu odpowiednim katalogom. Pozostałe wymagane warunki miałem spełnione. Nie miałem PECL HTTP i cURL ale to opcjonalne zależności bez, których wszystko, a przynajmniej fundamenty Kohany powinny ruszyć więc je zignorowałem. Zgodnie z instrukcją zmieniłem nazwę plikowi install.php i…

Otrzymałem komunikat błędu

HTTP_Exception_404 [ 404 ]: The requested URL project/index was not found on this server.

Popularny błąd 404 – not found – wydawałoby się i wszystko jasne. Tylko, że to pierwsze uruchomienie frameworka, w który jest wstępnie skonfigurowany, posiada domyślny kontroler „welcome” a w nim domyślną akcję „index” tak więc powinienem zobaczyć przyjazny napis „hello, world!”.

Gdyby to było moje pierwsze zetknięcie z frameworkiem Kohana to pewnie po pierwszych 5 min. dałbym sobie spokój. Nie dość, że przy pierwszym uruchomieniu dostajemy exceptiona to jeszcze tak naprawdę nic nam on nie mówi o możliwościach jego rozwiązania. W końcu kontroler jest tam gdzie trzeba, klasa ma odpowiednią nazwę i jest metoda, która zgodnie z dokumentacją powinna zostać wywołana.

Tymczasem przyczyną jest zła konfiguracja. W pliku /application/bootstrap.php mamy taki fragment kodu

Kohana::init(array(
    'base_url'   => '/',
));

Parametr base url w moim przypadku powinien wyglądać tak

Kohana::init(array(
    'base_url'   => '/project/',
));

Proste prawda? Tylko ktoś kto dopiero poznaje tę platformę się tego nie domyśli. Przyznaję się, że sam się trochę tego naszukałem, a to dlatego, że kiedyś już rozwiązałem ten problem kawałkiem uniwersalnego kodu i zdążyłem o nim zapomnieć. Tymczasem teraz stawiając nowy projekt na świeżutkiej Kohanie prosty problemik wrócił do mnie i trafił w głowę.

$dirname = dirname($_SERVER['SCRIPT_NAME']);
$base_url = preg_replace('@/+$@', '', $dirname=="\\"?'':$dirname).'/'; 
 
Kohana::init(array(
    'base_url'   => $base_url,
));

Dla purystów, którzy nie lubią zbędnych operacji i nie ufają zbytnio uniwersalności tego rozwiązania (w końcu było testowane tylko na serwerach z Apache 2) proponuję nieco zmodyfikowaną wersję

Kohana::init(array(
	'base_url'   => '/',
));
 
if (Kohana::$environment != Kohana::PRODUCTION) {
    $dirname = dirname($_SERVER['SCRIPT_NAME']);
    $base_url = preg_replace('@/+$@', '', $dirname=="\\"?'':$dirname).'/';
    if (Kohana::$base_url != $base_url) {
        throw new Kohana_Exception(sprintf('Perhaps you have a bad parameter set 
            base_url in bootstrap.php. Most likely, the correct value should be 
            "%s"', $base_url));
    }
}

Routing w Kohana nie jest szczytem elegancji, wygody i elastyczności. W stosunku do routingu w Django, każdy tego typu system w PHP jest prymitywną próbą naśladownictwa. Wynika to w dużej mierze z ograniczeń wyrażeń regularnych w PHP, które aż do wersji 5.2.2 (PCRE 7.0) – przynajmniej według dokumentacji – nie obsługiwały nazwanych podwzorców (named subpattern).

$str = 'foobar: 2008';
preg_match('/(?P<name>\w+): (?P<digit>\d+)/', $str, $matches);
print_r($matches);
//Array
//(
//    [0] => foobar: 2008
//    [name] => foobar
//    [1] => foobar
//    [digit] => 2008
//    [2] => 2008
//)

Ja mam na swoim kompie PHP 5.3.5 (PCRE 8.12) i dalej mi to nie działa.

Nie mniej routing w Kohana 3.1 wzbogacił się o możliwość definiowania ścieżek z użyciem funkcji lambda lub callback – w zależności od wersji PHP oczywiście. Daje to spore możliwości, których namiastkę spróbuję teraz zaprezentować.

Standardowe ustawienia routingu w Kohana Framework wyglądają następująco.

Route::set('default', '(<controller>(/<action>(/<id>)))')
	->defaults(array(
		'controller' => 'welcome',
		'action'     => 'index',
	));

Powoduje to, że do strony głównej serwisu możemy się odwołać w trojaki sposób.


http://domena.com/


http://domena.com/welcome/


http://domena.com/welcome/index

Jest to problem SEO tylko jeśli w projekcie budujemy różne linki odwołujące się do strony głównej. Jeśli dojdzie do tego wielojęzyczność do adresu zostanie dodany dodatkowy parametr (no chyba, że strony w poszczególnych językach trzymane są na subdomenach). Wtedy strona główna będzie występować w różnych wersjach językowych, a dodatkowo strona główna w języku domyślnym będzie w wersji z oznaczeniem języka i bez.


http://domena.com/


http://domena.com/pl-pl/

Dobrze byłoby zrobić aby strona główna w domyślnej wersji językowej była dostępna tylko pod adresem „/”. Natomiast próba wejścia przez uri „/pl-pl/” kończyła się przekierowaniem na „/”. Po wpisaniu w adres przeglądarki adresów „/welcome/” lub „/welcome/index” powinien być zgłoszony błąd 404.

Aby to osiągnąć stworzyłem klasę ProcessRoute w pliku /application/classes/processroute.php

<?php defined('SYSPATH') or die('No direct script access.');
 
class ProcessRoute {
    public static function main_page($uri) {
        $base_url = Kohana::$base_url;
        if ($uri == I18n::$lang) {
            header("Location: {$base_url}", true, 302); die();
        } else if ($uri == '') {
            if ($_SERVER["REQUEST_URI"] != $base_url) {
                throw new HTTP_Exception_404('Unable to find a route to match the URI: :uri'
                , array(':uri' => str_replace($base_url, '', $_SERVER["REQUEST_URI"])));
            }
            return array(
		        'lang' => 'pl-pl',
		        'directory' => '',
		        'controller' => 'welcome',
		        'action' => 'index',
	        );
        } else if (preg_match('/^[a-z]{2,2}-[a-z]{2,2}$/', $uri)) {
            return array(
		        'lang' => $uri,
		        'directory' => '',
		        'controller' => 'welcome',
		        'action' => 'index',
	        );
        }
        return false;
    }
}

Myślę, że dodatkowego wyjaśnienia wymaga jedynie fragment

if ($_SERVER["REQUEST_URI"] != $base_url) {
    throw new HTTP_Exception_404('Unable to find a route to match the URI: :uri'
    , array(':uri' => str_replace($base_url, '', $_SERVER["REQUEST_URI"])));
}

Otóż w przypadku wpisania w pole adresu przeglądarki urla „http://localhost/project/index/” wartość uri przekazana do metody ProcessRoute::main_page będzie miała pustą wartość „”. Jest to ewidentny bug i dlatego musiałem zastosować to nieeleganckie obejście.

Aby podpiąć wyżej zaprezentowaną klasę do routingu należy w bootstrapie dodać

/**
 * Set the routes. Each route must have a minimum of a name, a URI and a set of
 * defaults for the URI.
 */
Route::set('main', array('ProcessRoute', 'main_page'));

Na stronie głównej budowa witryny internetowej się nie kończy. Podpięcie funkcji zwrotnej pod routing daje o wiele większe możliwości. Załóżmy, że chcę wyświetlać dane kontaktowe różne dla różnych wersji językowych.

class Controller_Contact extends Controller {
 
    public function action_index()
    {
        $lang = Request::current()->param('lang');
        if ($lang == 'en-en') {
            $this->response->body('Contact');
        } else {
            $this->response->body('Kontakt');
        }
    }
} // Contact

Chciałbym aby polską wersję podpiąć pod adres „/pl-pl/kontakt”, a w wersję angielską pod „/en-en/contact”. W tym celu stworzyłem sobie plik konfiguracyjny /application/config/routes.php.

return array
(
	'pl-pl' => array(
	    'kontakt' => array(
		    'controller' => 'contact',
		    'action' => 'index',
	    )
	),
	'en-en' => array(
	    'contact' => array(
		    'controller' => 'contact',
		    'action' => 'index',
	    )
	)
);

Aby routing korzystał z tego pliku konfiguracyjnego do klasy ProcessRoute (w pliku /application/classes/processroute.php) dodałem statyczną metodę „static_pages”.

class ProcessRoute {  
    public static function main_page($uri) {
        // ...
    }
    public static function static_pages($uri) {
        // $lang = I18n::$lang;
        if (preg_match('/^([a-z]{2,2}-[a-z]{2,2})\/(.*)/', $uri, $matches) && count($matches) == 3) {
            $lang = $matches[1];
            // if ($lang == I18n::$lang) return false;
            $uri = $matches[2];
        }
        $routes = Kohana::config("routes")->as_array();
        if (!isset($lang) || !isset($routes[$lang])) {
            return false;
        } else {
            $routes = $routes[$lang];
        }
        if (array_key_exists($uri, $routes)) {
            $route = $routes[$uri];
            $route['lang'] = $lang;
            return $route;
        } 
        return false;
    }
}

Proszę zwrócić uwagę na wykomentowaną linię.

$lang = I18n::$lang;

Chciałem mieć pewność, żeby za wyjątkiem strony głównej na wszystkich podstronach był używany parametr wersji językowej. Mogę odkomentować wspomnianą linię i wtedy dane kontaktowe pojawią się zarówno po wybraniu adresu „/pl-pl/kontakt”, jak i „/kontakt”. Jeśli do tego odkomentuję jeszcze linię

if ($lang == I18n::$lang) return false;

wtedy z kolei dane kontaktowe w domyślnej wersji językowej będą dostępne jedynie pod adresem „/kontakt”.

Zapomniałbym. W bootstrapie trzeba jeszcze wywołać metodę ProcessRoute::static_pages.

Route::set('static_pages', array('ProcessRoute', 'static_pages'));

Jak pokazują te proste przykłady routing w Kohana 3.1 zyskał na elastyczności. Odrobina pracy i można by z tego mechanizmu wycisnąć dużo więcej. Panel administracyjny można oprzeć o tradycyjny routing oparty na kontrolerach i akcjach z kolei część publiczną adresować z wykorzystaniem plików konfiguracyjnych lub też trzymać ścieżki w bazie danych.

 
3 Comments

Posted in PHP