netcoffee.pl*po godzinach - reaktywacja

Ten blog jest kontynuacją bloga dostępnego ongiś pod adresem netcoffee.pl/pogodzinach.
Artykuły, które na to zasługują są przenoszone do nowej wersji bloga. Pozostałe wkrótce znikną.

2010-06-27

Magia w PHP - Nowy artykuł po reaktywacji bloga

To jest pierwszy artykuł napisany po przeniesieniu bloga z adresu netcoffee.pl/pogodzinach.

W ramach eksperymentu "co można wycisnąć z magicznych funkcji PHP" postanowiłem sprawdzić, na ile można ułatwić sobie pobieranie/aktualizowanie jednej konkretnej wartości z bazy danych.

Założenia były takie:

  • istnieje klasa db, która jest odpowiedzialna za komunikację z RDBS
  • pobrać można wartość tylko jednego pola z jednego rekordu 
  • wybór rekordu odbywa się przez podanie wartości pola id
  • pobranie/aktualizowanie wartości musi być możliwie proste
Udało mi się uzyskać efekt, pozwalający pobrać wartość takim zapisem:

$iAge = $db -> contact[17] -> age;


Aktualizacja wartości to z kolei:

$db -> contact[17] -> age = 18;


Przy czym:

  • $db - to już istniejący obiekt zapewniający dostęp do bazy
  • contact - nazwa tablicy w bazie
  • 17 - wartość pola id
  • age - nazwa pola pobieranego/aktualizowanego
Jak to uzyskałem?

Tytułem wstępu:
  • magiczna metoda __get() w klasie pozwala obsłużyć sytuację, w której użytkownik obiektu próbuje pobrać wartość nie istniejącej składowej (lub składowej nie będącej publiczną). W takim przypadku zostanie wykonana metoda __get() - jako parametr otrzyma nazwę składowej. Jej wynik zostanie zwrócony jako wartość składowej
  • magiczna metoda __set() w klasie działa podobnie - pozwala ustawić wartość niedostępnej składowej. Otrzymuje dwa parametry - nazwę i nową wartość składowej
  • interfejs ArrayAccess pozwala obsłużyć sytuację, w której obiekt jest traktowany jak tablica. Metoda offsetGet() jest wywoływana w momencie, kiedy użytkownik obiektu próbuje pobrać konkretny element tablicy
A teraz - jak to działa?
  • magiczna funkcja __get() w klasie db, zwraca obiekt klasy magdb_table, przekazując do konstruktora nazwę pobieranej składowej (czyli nazwę tabeli) oraz obiekt tej klasy na którym wywołano metodę (czyli $this)
  • klasa magdb_table implementuje interfejs ArrayAccess - pozwala nam to "przejąć" indeks będący potrzebny do określenia rekordu, z którego pobieramy dane. Metoda offsetGet() zwraca obiekt klasy magdb_row, przekazując do jego konstruktora niezbędne informacje: obiekt $db, nazwę tablicy (to otrzymaliśmy w konstruktorze) oraz indeks - ten metoda otrzymała w parametrach wywołania
  • na koniec zostajemy z obiektem klasy magdb_row. Otrzymał on w konstruktorze: obiekt $db (pozwalający na dostęp do bazy), nazwę tabeli z której pobieramy dane (ew. aktualizujemy), id wiersza (to jest uproszczenie - dokładnie rzecz ujmując, otrzymaliśmy wartość której możemy użyć w klauzuli where, zakładając, że kolumna ID ma unikalne wartości).  Ostatnią rzeczą która pozostała do zrobienia, to użycie metod __get() i __set() do pobrania/zaktualizowania danych w bazie. Metoda __get() otrzymuje nazwę zmiennej (czyli nazwę pola) a metoda __set() dodatkowo nową wartość
Poniżej - kod. Wyjęty z istniejącego projektu i "oczyszczony" ze wszystkiego, co nie stanowi meritum sprawy. Działający w oryginale - ten poniżej nie testowany. Przed użyciem trzeba dodać oczywiście kontrolę błędów, kontrolę wartości (szczególnie indeksu), no i oczywiście samo pobieranie/aktualizowanie danych. 

Co dalej? To był tylko eksperyment, ale w ramach jego kontynuacji można by:
  • obsłużyć offsetUnset() w magdb_table - pozwalając na usuwanie rekordów
  • obsłużyć offsetSet() w magdb_table - pozwalając na tworzenie rekordów (lub aktualizację kilku wartości za jednym zamachem) przez podanie tablicy asocjacyjnej 




class db{
  public function __get($name){
    return new magdb_table($name, $this);
  }
}

class magdb_table implements ArrayAccess{

  private $name;
  private $db;

  public function __construct($sName, $db){
    $this -> name = $sName;
    $this -> db = $db;
  }

  public function offsetExists($offset){}
  public function offsetUnset($offset){}
  public function offsetSet($offset, $value) {}

  public function offsetGet($offset) {
    return new magdb_row($this -> name, $offset, $this -> db);
  }
}

class magdb_row{
  private $table;
  private $rowId;
  private $db;

  public function __construct($sTable, $iRowId, $db){
    $this -> db = $db;
    $this -> table = $sTable;
    $this -> rowId = $iRowId;
  }

  public function __get($sName){
    // sql goes here 

    // select $sName from $this -> table where id = $this -> rowId
    // $sName - column name
    // $this -> table - table name
    // $this -> rowId - id value
  }

  public function __set($sName, $sValue){
    // sql goes here 

    // update $this -> table set $sName where id = $this -> rowId
    // $sName - column name
    // $this -> table - table name
    // $this -> rowId - id value
  }
}

2008-09-17

Łatwiejsze debugowanie z FirePHP

Problem...

Często debugując kod wypisujemy wartość jakiejś zmiennej do treści strony. Takie podejście ma tę wadę, że te wstawki trzeba usunąć z finalnej wersji. Czasem też format zwracanych danych uniemożliwia nam swobodne dodawanie własnego tekstu (XML, JSon no i oczywiście generowane dynamicznie grafiki). Do tego dochodzi praca z Ajaksem - wyników pobranych przez XmlHTTPRequest nie widzimy...


... rozwiązany

Skoro więc nie możemy przesłać dodatkowych komunikatów w wyniku, to może wykorzystać nagłówki HTTP? Jak się okazuje, jest to nie tylko możliwe, ale otrzymujemy gotowe rozwiązanie w postaci dodatku do Firefoksa - FirePHP i biblioteki PHP. Strona projektu - firephp.org. Na stronie FirePHP dostępne są informacje, jak zintegrować bibliotekę z wieloma znanymi frameworkami (CakePHP, CodeIgniter, Drupal, Kohana, ExpressionEngine, PRADO, Symfony, TYPO3, Zend Framework).


Jak to działa na serwerze?

Najpierw w kodzie wykorzystujemy bibliotekę FirePHP (wymagane PHP 5). Do dyspozycji mamy całą klasę FirePHP lub bardzo wygodną funkcję fb(). Najprostsze wywołanie to fb('Hello World'); ale można też bardziej skomplikowanie (np. wypluć całą tablicę lub obiekt) - użycie funkcji fb(). Całość informacji jakie podamy przy kolejnych wywołaniach funkcji fb(); jest przesyłana w formacie JSon w szeregu nagłówków HTTP X-FirePHP-Data-


Jak to działa w przeglądarce?

Teraz trzeba przesłane dane wyświetlić w jakiś sensowny i czytelny sposób. To właśnie robi dodatek FirePHP do Firefoksa. Trzeba pamiętać, że FirePHP wymaga innego dodatku - Firebug. Gdy w odpowiedzi serwer przyśle nagłówki X-FirePHP-Data-, FirePHP połączy ich wartość w całość a zinterpretowany wynik wyświetli w konsoli Firebug.

Konsola Firebug z wynikami FirePHP


Zalety

Podstawową zaletą jest to, że dane potrzebne do debugowania nie muszą być osadzone w treści odpowiedzi. Dodatkowo, są prezentowane w czytelny sposób w jednym miejscu. Bardzo pomocne jest to, że dane debugowe można dołączyć do danych, które "gryzą się" z różnymi wstawkami (np. dane binarne, obrazki, XML, JSON etc.). Nawet jeżeli dane nie zostaną przesłane w nagłówkach strony (a np. w nagłówkach przesłanych w odpowiedzi pobranej z wykorzystaniem XMLHttpRequest lub w nagłówkach przysłanych z obrazkiem osadzonym na stronie) to i tak wynik zobaczymy w konsoli. No i ostatnie - nawet jeżeli zapomnimy usunąć wpisy debugowe, to i tak pozostaną one niewidoczne dla nie wtajemniczonych.


Wady

Nie widzę... ;)


Ciekawe co dalej...

Dostępny jest patch do Apache Mod Rewrite, wysyłający do FirePHP rewrite log. Niestety patch został napisany przez niedoświadczonego programistę C i jest w stadium proof-of-concept. Miejmy nadzieję, że projekt się rozwinie.

Z informacji na stronie projektu wynika, że FirePHP może być także użyty w połączeniu z ASP, Pythonem czy środowiskiem Jaxer.

Na stronie w dosyć szczegółowy sposób opisano jak formułować dane wysyłane do FirePHP, nic więc nie stoi na przeszkodzie, aby znaleźć nowe zastosowania dla tego rozszerzenia.

2008-09-14

Serwer w PHP

Dla wszystkich jest oczywiste, że PHP działa jako element serwera WWW. Jednak PHP może samo w sobie działać jako prosty serwer (nie koniecznie HTTP). Dla takiego rozwiązania najlepiej wykorzystać wersję CLI PHP (Command Line Interface)


Dlaczego serwer w PHP?

W jednym z ostatnich projektów, byłem zmuszony "wynieść" część funkcjonalności systemu z serwera linuksowego (PHP, Apache, MySQL) na inny komputer, działający pod kontrolą systemu Windows. Podstawowym zagadnieniem w tym momencie stała się komunikacja między głównym systemem, a tą wydzieloną cząstką. Po rozważeniu kilku możliwości, zdecydowałem się napisać prosty serwer z którym można połączyć się przez TCP/IP, wydać polecenie i odczytać wynik.


Jak to ma działać?

Zasada działania jest prosta - skrypt PHP działający pod Windows (nazwijmy go "Sonda") nasłuchuje na porcie 9876, akceptuje przychodzące połączenia i wymaga autoryzacji od klienta. Po poprawnej autoryzacji możliwe jest wysłanie polecenia i odebranie wyników. Na samym końcu następuje wylogowanie i rozłączenie.


Kod źródłowy


set_time_limit(0);
$sckMain = socket_create(AF_INET, SOCK_STREAM, 0) or die();
socket_bind($sckMain, '192.168.1.101', 9876) or die();
socket_listen($sckMain) or die();
$oaClients = array();
while(true){
$sckaRead = array();
$sckaRead[0] = $sckMain;
foreach($oaClients as $x => $oClient){
if($oClient -> bClosed == true){
unset($oaClients[$x]);
}else{
$sckaRead[] = $oClient -> sckSocket;
}
}
socket_select($sckaRead, $null = null, $null = null, 0);
if(in_array($sckMain, $sckaRead)){
$sckNewClient = socket_accept($sckMain);
$oaClients[] = new clientHandler($sckNewClient);
}
foreach($oaClients as $oClient){
if(in_array($oClient -> sckSocket, $sckaRead)){
$oClient -> handle();
}
}
}
socket_close($sckMain);
class clientHandler{
var $bClosed = false;
var $sckSocket = '';
var $bAuthenticated = false;
function clientHandler($sckSocket){
$this -> sckSocket = $sckSocket;
$this -> send('HELLO');
}
function handle(){
$sCommand = $this -> read();
list($sExecute, $sParameters) = explode(' ', $sCommand, 2);
if($sCommand != ''){
if($this -> bAuthenticated == true){
if($sCommand == 'logout'){
$this -> disconnect();
}elseif($sExecute == 'echo'){
$this -> send($sParameters);
}elseif($sExecute == 'time'){
$this -> send(date('Y-m-d H:i:s'));
}else{
$this -> send('unknown command');
}
}elseif($sExecute == 'login'){
if($sParameters == 'powiedz-przyjacielu-i-wejdz'){
$this -> bAuthenticated = true;
$this -> send('login ok');
}else{
$this -> send('login error');
$this -> disconnect();
}
}else{
$this -> send('authenticate first');
}
}
}
function send($sMessage){
socket_write($this -> sckSocket, $sMessage . "\r\n" . chr(0));
}
function read(){
return trim(@socket_read($this -> sckSocket, 1024));
}
function disconnect(){
$this -> send('bye');
$this -> bClosed = true;
socket_close($this -> sckSocket);
}
}


Analiza

Aby można było nawiązać wiele połączeń jednocześnie, stworzymy jedno "główne" gniazdo (ang. socket) przyjmujące nowe połączenia i przekazujące je do nowych gniazd obsługujących połączenia. Zaczynamy więc:



$sckMain = socket_create(AF_INET, SOCK_STREAM, 0) or die();
socket_bind($sckMain, '192.168.1.101', 9876) or die();
socket_listen($sckMain) or die();


socket_create() tworzy nowe gniazdo, socket_bind() łączy je z portem 9876 i adresem IP 192.168.1.101, socket_listen() powoduje, że gniazdo zaczyna nasłuchiwać. Dodatkowo będziemy potrzebowali tablicy obiektów ($oaClients) obsługujących poszczególne połączenia.


Całość obsługi zamykamy w nieskończonej pętli (while(true)). Głównym jej elementem jest funkcja socket_select(). Jej opis w manualu PHP jest dosyć enigmatyczny ("Runs the select() system call on the given arrays of sockets with a specified timeout"). W praktyce wygląda to następująco: socket_select() przyjmuje jako trzy pierwsze wartości tablice gniazd (arrays of sockets) (a dokładnie rzecz ujmując funkcja przyjmuje parametry przez referencję). Funkcja monitoruje stany gniazd i w momencie gdy ulegnie zmianie status któregoś z gniazd kończy swoje działanie. Ważne jest to, że funkcja modyfikuje przekazane tablice tak, aby zawierały w sobie tylko te gniazda, które zmieniły swój status. Gniazda w pierwszej tablicy będą monitorowane pod kątem dostępności danych (a dokładnie, czy czytanie z tego gniazda nie zablokuje programu). Jako pozostałe parametry podamy NULL, gdyż w tej chwili interesuje nas tylko czytanie z gniazd.


Przed wywołaniem socket_select() musimy więc przygotować sobie tablicę gniazd do monitorowania:



$sckaRead = array();
$sckaRead[0] = $sckMain;
foreach($oaClients as $x => $oClient){
if($oClient -> bClosed == true){
unset($oaClients[$x]);
}else{
$sckaRead[] = $oClient -> sckSocket;
}
}


Jako pierwszy element monitorowanej tablicy dodajemy główne gniazdo (aby sprawdzić, czy nowe połączenie nie czeka na akceptację). W pętli przeszukujemy tablicę obiektów obsługujących połączenia. Jeżeli obiekt zamknął połączenie, usuwamy go z tablicy (unset). Jeżeli nie - dodajemy gniazdo do monitorowanej tablicy.



if(in_array($sckMain, $sckaRead)){
$sckNewClient = socket_accept($sckMain);
$oaClients[] = new clientHandler($sckNewClient);
}
foreach($oaClients as $oClient){
if(in_array($oClient -> sckSocket, $sckaRead)){
$oClient -> handle();
}
}


Po wywołaniu socket_select() sprawdzamy, czy w tablicy $sckaRead znajduje się główne gniazdo. Jeżeli tak, oznacza to, że nowe połączenie oczekuje na akceptację. Wtedy wywołujemy funkcję socket_accept() która przyjmuje połączenie i zwraca nowe gniazdo obsługujące to połączenie. Tworzymy nowy obiekt klasy clientHandler i dodajemy go do tablicy klientów. W kolejnej pętli sprawdzamy czy gniazdo poszczególnych połączeń znajduje się w tablicy gniazd z których możemy czytać. Jeżeli znajduje się - wywołujemy metodę handle() obiektu. Zadaniem tej metody jest przeczytanie "polecenia" z gniazda i odpowiedź na nie.


Klasa clientHandler jest dosyć prosta. Jej główną metodą jest handle() która czyta polecenie z gniazda, wykonuje je i zwraca wynik do klienta. Całość klasy nie wymaga chyba większego komentarza, dlatego jej analizę pozostawiam Wam.


Na koniec - zagadka

Na zakończenie tego króciutkiego wpisu zagadka: Dlaczego musiałem wynieść część funkcjonalności na inny komputer i co w oryginalnym projekcie robi program "Sonda"?

2008-03-30

Szaf(k)a gra!

Mam już na koncie całkiem sporo projektów - dużych i małych i tych napisanych "do szuflady". Każdy staram się wykonać jak najlepiej, ale zawsze jest tak, że projekt albo "mi leży" albo "mi nie leży". Najbardziej lubię projekty, które wychodzą poza środowisko serwera web i wchodzą w interakcję z otoczeniem mniej lub bardziej "fizycznym".

Najnowszym takim projektem - cały czas we wczesnej fazie dev ;) - jest domowa skrzynka muzyczna sterowana przez web.

Jak to działa?

Sercem jest stary komputer z prockiem AMD Duron 900 MHz ustawiony w szafce w kuchni, żeby w pokoju była cisza. Do niego podłączone są głośniki ustawione już w pokoju. Całość podłączona jest do Sieci, dzięki czemu mogę sterować muzyką przez komórkę z Operą Mini, a w przypływie ochoty mogę posłuchać radia internetowego (np. Groove Salad).

Jak to jest zrobione?

Całkiem prosto. Oto lista potrzebnych rzeczy i softu:
  • komputer - 1 sztuka
  • kabel skrętka - w zależności od potrzeb, w moim przypadku 15 m
  • przedłużacz audio (mały jack) - w zależności od potrzeb
  • system operacyjny - Linux (ja użyłem dystrybucji Debian)
  • mpd (music player daemon) - soft grający, działa jako demon, nie ma interfejsu i sterowany jest przez sieć
  • mpc (konsolowy soft sterujący mpd)
  • mplayer - do odtwarzania stacji radiowych
  • serwer WWW (Apache) z obsługą PHP
  • konto na no-ip.org


Krok I

- instalujemy na komputerze Debiana (lub co tam wolicie), do tego mpd i mpc. I tutaj mała uwaga - dostępna przez apt-get wersja jest nieco stara i ma bug powodujący, że po zatrzymaniu ogdrywania (np. pauza) nie można wznowić odgrywania. Dlatego należy całość skompilować ze źródeł. Strona projektu: musicpd.org

Krok II

- komputer wynosimy tam, gdzie będzie najmniej przeszkadzał, podłączamy do sieci, podłączamy głośniki. Rejestrujemy się na no-ip.com i instalujemy klienta no-ip.com na maszynie

Krok III

- sterować możemy z konsoli, używając klienta mpc. Nie o to jednak chodziło. Ja interfejs zbudowałem w oparciu o PHP. Od strony użytkownika jest to przeglądarkowe klikadło wykorzystujące Ajax (jquery.com - świetnie działa w Operze Mini). Jeżeli zaś chodzi o komunikację z mpd - połączenie z portem 6600 localhosta. MPD ma własny, dość prosty protokół. Do sterowania playerem przygotowałem prostą klasę:


<?php
class ncmpdcommander{
var $fpStream;

function ncmpdcommander($sServer, $iPort){
$this -> fpStream = fsockopen($sServer, $iPort);
//stream_set_blocking($this -> fpStream, 0);
$sResoponse = fread($this -> fpStream, 1024);
}

function disconnect(){
fclose($this -> fpStream);
}

function doCommand($sCommand){
fwrite($this -> fpStream, $sCommand . "\n");

while(trim($sLine) != 'OK'){
$sLine = fgets($this -> fpStream, 512);
if(trim($sLine) != 'OK'){
$sResponse .= $sLine;
}
}
return $sResponse;
}


function playbackNext(){
$this -> doCommand('next');
}
function playbackPrev(){
$this -> doCommand('previous');
}
function playbackStop(){
$this -> doCommand('stop');
}
function playbackPlay(){
$this -> doCommand('play');
}
function playbackPause(){
$this -> doCommand('pause');
}

function getTitle(){
$sStatus = $this -> doCommand('currentsong');
preg_match('!file: (.+)!', $sStatus, $shFound);
$sTitle = $shFound[1];
preg_match('!([^/]+)\.mp3$!', $sTitle, $shFound);
$sTitle = $shFound[1];
$sTitle = preg_replace('!^[0-9 _-]+!', '', $sTitle);
$sTitle = str_replace('_', ' ', $sTitle);

return $sTitle;
}

function getPlaylist(){
$sPlaylist = $this -> doCommand('playlist');
$saPl = explode("\n", $sPlaylist);
foreach($saPl as $sItem){
list($iNum, $sTitle) = explode(':', $sItem, 2);
$saPlaylist[$iNum] = $sTitle;
}
return $saPlaylist;
}

function volumeUp(){
$this -> doCommand('volume +10');
}
function volumeDown(){
$this -> doCommand('volume -10');
}
}

?>



Samo wykorzystanie klasy jest już banalne:

<?php
require_once('ncmpdcommander.class.php');

$oCmd = new ncmpdcommander('localhost', 6600);
switch($_GET['cmd']){
case 'next':
$oCmd -> playbackNext();
break;
case 'prev':
$oCmd -> playbackPrev();
break;
case 'stop':
$oCmd -> playbackStop();
break;
case 'pause':
$oCmd -> playbackPause();
break;
case 'play':
$oCmd -> playbackPlay();
break;

case 'get-title':
echo $oCmd -> getTitle();
break;

case 'volume-up':
$oCmd -> volumeUp();
break;

case 'volume-down':
$oCmd -> volumeDown();
break;
}


$oCmd -> disconnect();
?>



To już koniec

Nie opisałem całości w szczegółach, mam jednak nadzieję, że ten opis zainspiruje Was do przygotowania czegoś podobnego. A może już coś takiego macie? Piszcie w komentarzach o Waszych projektach wychodzących poza przeglądarkę!

Pozdrawiam i do następnego wpisu!

2007-09-16

Domena z www - nie twórzmy niepotrzebnych ograniczeń

Problem

Po wpisaniu w Google zapytania jak wybrać domenę, otrzymamy w przybliżeniu 1,320,000 wyników. Jak widać, jest dość popularne zagadnienie. Każdy, kto zakłada własną stronę, może dowiedzień się jak wybrać piękną, unikalną domenę, sugerującą korzyści, jakość produktu, łatwo wymawianą, łatwą do zapamiętania... bla bla bla. Część z tych artykułów ociera się już o bełkot, choć można przeczytać także coś wartościowego.


Dziwi mnie jednak, dlaczego tak wielu administratorów stron (adminów, webmasterów i im podobnych) nie zauważa problemu subdomeny www? Bardzo często spotykam się z sytuacją, gdy jedynym działającym adresem strony jest www.example.org, natomiast example.org nie wskazuje na żaden serwer, albo - co gorsze - wskazuje na stronę firmy hostingowej.


Dziwi mnie niesamowicie to, że wiele firm walczy o jak najkrótsze nazwy, nieraz odkupując domeny za duże pieniądze. Po czym cały efekt jest psuty przez nieudolnego admina/webmastera etc, bo do ślicznej, trzyliterowej domeny trzeba dopisać kolejne 4 znaki - www. Złego wizerunku strony dopełnia fakt, że w gros przypadków nie ma żadnego uzasadnienia w wymuszaniu na użytkownikach dopisywania www.


Osobiście wpisując adresy stron, niemal zawsze pomijam www. Często niestety kończy się to koniecznością ponownego wpisania adresu - tym razem z www. Irytuje mnie to bardziej niż "strona przygotowana dla IE 5¼". Szczególnie, że rozwiązanie problemu jest niesamowicie banalne, a zaniechanie - w mojej opinii - wynika jedynie z ignorancji adminów.


Rozwiązanie I (dobre, z wadami) - dajemy wybór internautom

Dosyć dobrym rozwiązaniem jest takie skonfigurowanie sewera www, aby dla obu adresów (example.org i www.example.org) zwracał tę samą stronę. W Apache można np. skonfigurować dwa wirtualne hosty wskazujące na to samo położenie dokumentów - DocumentRoot.


Dzięki takiemu rozwiązaniu, obojętnie czy odwiedzający wpisze www.example.org czy example.org - zawsze zobaczy stronę której szuka.


Wady rozwiązania I

Internauta jest zdezorientowany

Mniej doświadczeni internauci (np. ci, którzy na e mówią "Internet") mogą czuć sie zdezorientowani, a nawet zagrożeni tym, że jakaś strona ma adres bez www na początku.

Możliwe rozwiązania: wykorzystać Rozwiązanie II lub zignorować problem ;)


Przeglądarka jest zdezorientowana

Należy pamiętać o tym, że przeglądarka odsyła ciasteczka tylko do tych domen, do których powinna je odesłać. Jeżeli więc w PHP ustawiamy ciasteczko w ten sposób:


setcookie('kruche', 'z_cukrem');

to nie dziwmy się, że użytkownik który np. zalogował się na example.org/login/, nie będzie zalogowany na www.example.org/.

Możliwe rozwiązania: ustawiajmy ciasteczka korzystając z opcjonalnych parametrów setcookie:


bool setcookie ( string $name [, string $value [, int $expire [, string $path [, string $domain [, bool $secure [, bool $httponly]]]]]])

Czyli możemy napisać tak:


setcookie('kruche', 'z_cukrem', time() + 60 * 60 * 24 * 31, '/', '.example.org');

ciasteczko będzie widoczne dla wszystkich subdomen naszego serwisu.

Takie podejście do ciasteczek jest bardzo ważne. Znam serwis, który działa pod dwoma adresami: example.org i www.example.org. Można w tym serwisie dokonać płatności za zamówienie za pośrednictwem jednego z systemów płatności online. Jednak powrót z systemu transakcyjnego następuje zawsze na adres www.example.org. Wiadomo jaki jest efekt: gdy loguję się na example.org - zamiast potwierdzenia płatności, otrzymuję ekran logowania...


Innym problemem jest zapamiętywanie haseł i wartości wpisanych w formularzach przez przeglądarki. Wartości te są także przypisywane do domen. Jeżeli zapamiętam hasło na example.org/login/, to nie będzie ono proponowane przez przeglądarkę na www.example.org/login/

Możliwe rozwiązania: Rozwiązanie II


serwer jest zdezorientowany

Przy korzystaniu z dwóch wirtualnych hostów, w zależności od konfiguracji serwera, możemy mieć problem np. ze statystykami (odzielne statystyki dla www.example.org i example.org) lub ze skonfigurowaniem SSL. Niektóre roboty i wyszukiwarki mogą traktować adresy www.example.org i example.org jako niezależne strony (choć zdaje się, że Google już sobie z tym poradziło).


Możliwe rozwiązanie: Rozwiązanie II


Rozwiązanie II (dobre) - odbieramy wybór internautom

W tym rozwiązaniu należy dać wybór internautom, przez skonfigurowanie serwera tak, aby działały oba adresy (www.example.org i example.org) - tak jak w Rozwiązaniu I - a następnie odebrać wybór przez ustawienie przekierowania na jednym z adresów. Takie rozwiązanie możemy zrealizować na dwa sposoby:


Apache i mod_rewrite

Używamy mod_rewrite w Apache aby przekierować użytkownika z example.org na www.example.org. Można to zrobić np. tak:


<VirtualHost 127.0.0.1>
ServerName example.org
DocumentRoot /var/www/i/co/tam/jeszcze_chcecie
RewriteEngine On
RewriteRule ^/(.*)?$ http://www.example.org/$1?%{QUERY_STRING} [R=301,L]
</VirtualHost>


Moje ulubione rozwiązanie. Jest o tyle dobre, że przekieruje użytkownika bez utraty ścieżki i parametrów przekazanych metodą GET. Wady: dane przekazane metodą POST zostaną utracone.


PHP i header()

Jeżeli nie macie możliwości modyfikowania konfiguracji Apache, podobne rozwiązanie można uzyskać korzystając z PHP i funkcji header():


if($_SERVER['HTTP_HOST'] == 'example.org'){
header("Location: http://www.example.org{$_SERVER['REQUEST_URI']}");
die();
}



i po problemie!

Jak widać, można bez problemu skonfigurować serwer tak, aby działały obie domeny - www.example.org lub example.org. Cała trudność polega jedynie na zauważeniu problemu. W zależności od potrzeb możemy skorzystać z któregoś z rozwiązań. W obu jednak trzeba pamiętać o tym, że serwery DNS muszą zwracać IP serwera dla obu domen.


Apel dnia ;)

Nie narzucajmy sztucznych i niepotrzebnych ograniczeń!

Takim niepotrzebnym ograniczeniem jest właśnie wymaganie wpisywania www lub to o którym pisał kiedyś Robert Drózd: Za krótkie - źle, za długie - też źle.

2007-09-13

2^8 dzień roku

W 28 dniu roku, wszystkim programistom życzę wszystkiego najlepszego z okazji Dnia Programisty. Ciekawy jestem, jak go obchodzicie…

2006-12-03

Czyżby TODO idealne?

TODO: znaleźć TODO

Od bardzo długiego czasu, poszukiwałem programu, który pomógłby mi zarządzać listą zadań do zrobienia - TODO. Zainstalowałem wiele programów dla Windows, oraz systemów stworzonych w PHP i opartych o MySQL. Niestety, żaden z nich nie spełnił moich wymagań. Jak się okazało, autorzy popadali z jednej skrajności w drugą. Programy były albo bardzo ubogie, oferując jedynie możliwość tworzenia prostego spisu zadań, albo były bardzo rozbudowane. Przy czym wadą tych rozbudowanych było to, że wymagały od użytkownika podawania wszystkich istnejących parametrów zadań.

Jednym z bardziej złożonych systemów, był Eventum, udostępniony przez MySQL AB. Jego rozbudowane opcje budziły szacunek, niestety, aby rozpocząć używanie systemu, należało skonfigurować bardzo dużo parametrów - projekty, uczestnicy, klienci, statusy, priorytety itp. Skończyłem używać ten system zanim jeszcze tak naprawde zacząłem.

DONE: znaleźć TODO

W końcu, dzięki - o ile dobrze pamiętam - serwisowi dzone.com - znalazłem program, który ma szansę zostać moim TODO idealnym. Jest to ToDoList (obecnie w wersji 5.0.1) udostępniony przez AbstractSpoon Software.

ToDoList

Główne okno programu zawiera:

Todo - okno główne



  • listę zadań w postaci drzewa
  • panel z parametrami aktualnie wybranej pozycji
  • pole komentarza (RTF)
  • panel wyszukiwania

Dla każdego z zadań, można zdefiniować parametry:
  • priorytet (0-10)
  • ryzyko (0-10)
  • procent wykonania
  • przewidywany czas potrzebny na wykonanie
  • rzeczywisty czas poświęcony na wykonanie
  • datę rozpoczęcia pracy
  • datę rzeczywistego zakończenia pracy
  • termin wykonania
  • osobę, której przypisano zadanie
  • osobę, ktora przypisała zadanie
  • status (dowolnie definiowana lista)
  • kategoria (dowolnie definiowana lista)
  • zewnętrzne ID
  • koszt
  • zależność od innego zadania
  • kolor
  • komentarz
Dodatkowo, każde z zadań można oznaczyć flagą, oraz przypisać do niego plik.
Bardzo ważną zaletą programu jest to, że wszystkie parametry zadania są opcjonalne. Dzięki temu o wiele łatwiej można dostosować program do własnych potrzeb. Jeżeli nie mamy w zwyczaju określania terminu wykonania zadania - to go nie musimy definiować. Co nie oznacza, że gdy przyjdzie taka potrzeba, nie będziemy mogli tego zrobić.

Wszystkie parametry zadania definiujemy w panelu znajdującym się pod listą:
szczegóły zadania

Zadania można organizować w hierarchiczne drzewo z wieloma poziomami zagłębienia.
lista zadań
Zaznaczenie nadrzędnego zadania jako wykonanego, może powodować zaznaczenie poniższych zadań jako wykonane. Zadania można przemieszczać na liście względem innych, używając klawiszy kursora z wciśniętym klawiszem Control.

Kilka parametrów zadań zasługuje na dodatkowe kilka zdań:
priorytet
każdemu priorytetowi można przypisać kolor, lub wszystkim można przypisać kolejne etapy przejścia tonalnego między dwoma kolorami. Dodtakowo, zadanie nadrzędne może automatycznie otrzymać najwyższy priorytet zadań podrzędnych (można wykluczyć priorytet zadań wykonanych)
procent wykonania
ten parametr można oznaczać ręcznie, może on być też wyliczony na podstawie wykonania zadań podrzędnych. Podstawą obliczeń może być albo ilość zadań wykonanych/niewykonanych lub czas potrzebny i poświęcony na ich wykonanie.
rzeczywisty czas poświęcony na wykonanie
może być wyrażony w kilku jednostkach (minuty, godziny, dni, tygodnie, miesiące, lata). Dodatkowo program umożliwia automatyczne mierzenie czasu poświęconego na zadanie (pomiar jest wstrzymywany na czas działania wygaszacza ekranu).
status/kategoria
można przypisać dowolny status/kategorię. Wcześniej wprowadzone wartości tworzą listę, z której można szybko wybrać status/kategorię.
zależność
można zdefiniować ID zadania, które musi być wykonane przed wybranym zadaniem. Jeżeli spróbujemy oznaczyć zadanie zależne jako wykonane, a zadanie nadrzędne nie będzie wykonane, zostanie wyświetlone ostrzeżenie.
komentarz
jest edytowany w polu RTF, pozwalającym na formatowanie tekstu, a także na wstawianie odnośników do innych zadań, w postaci tdl://xxx gdzie xxx jest identyfikatorem zadania. Odnośnik może prowadzić także do zadań zdefiniowanych w innych plikach. Pole komentarza wygląda tak:
komentarze

Praca zespołowa
ToDoList umożliwia także współdzielenie pliku przez kilka osób. Wystarczy plik z listą zadań umieścić na dysku sieciowym. Aby wykluczyć konflikty w trakcie edycji, przed edycją pliku, należy wcześniej zablokować możliwość edycji innym użytkownikom:

Tryb blokowania

Wcześniej w opcjach trzeba włączyć opcję "Enable simple source control". Pracę kilku osób ułatwiają dodatkowe opcje:
  • automatyczne ponawianie próby zablokowania pliku do edycji, w przypadku gdy edycję zablokował inny użytkownik
  • automatyczne odblokowywanie edycji przy zamykaniu listy
  • odblokowywanie listy, gdy nie dokonano zmian przez ustalony czas
  • automatyczne sprawdzanie statusu listy co ustalony czas i automatyczne wczytywanie aktualnego pliku

Dodatkowe opcje
W ustawieniach można zdefiniować globalny skrót klawiaturowy przywołujący i minimalizujący program, który może minimalizować się do ikony systemowej. Dzięki temu łatwo operuje się programem, który czekając w gotowości, nie rozprasza uwagi, nie zabiera miejsca na pasku systemowym, i co ważniejsze - na liście okien (Alt-Tab).

Warto także wspomnieć o tym, że:
  • istnieje możliwości eksportu/importu list zadań (HTML, MLO, Outline, GanttProject, iCalendar, ...)
  • istnieje możliwości dodawania pluginów i narzędzi zewnętrznych (tu można wykorzystać parametr external ID)
  • lista jest przechowywana jako plik XML, można więc z łatwością stworzyć własne narzędzie potrafiące odczytywać plik
  • można w samym programie przetwarzać listę przy użyciu szablonu XSLT


To jest tylko pobieżne przedstawienie programu ToDoList. Zainstalowałem naprawdę sporo programów tego typu, ten jednak wydał mi się najlepszy z dotąd testowanych i godny polecenia. Nie twierdzę jednak, że jest najlepszy ze wszystkich istniejących. Dlatego właśnie chciałbym dowiedzieć się, czego używacie jako menadżera zadań? Co lubicie w tych programach, a co was drażni? Jakie macie wymagania względem takich programów? A może macie jakieś własne rozwiązania?