<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;C0EDR3k7eSp7ImA9WhRaFEw.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923</id><updated>2012-02-16T18:01:16.701+01:00</updated><title>netcoffee.pl*</title><subtitle type="html">*po godzinach</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://pogodzinach.netcoffee.pl/" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>17</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/netcoffee_pl_po_godzinach" /><feedburner:info uri="netcoffee_pl_po_godzinach" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;DUAMSHw4fSp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-3313149111782080286</id><published>2010-06-27T02:16:00.000+02:00</published><updated>2010-06-27T02:16:29.235+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T02:16:29.235+02:00</app:edited><title>Magia w PHP - Nowy artykuł po reaktywacji bloga</title><content type="html">To jest pierwszy artykuł napisany po przeniesieniu bloga z adresu netcoffee.pl/pogodzinach.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Założenia były takie:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;istnieje klasa db, która jest odpowiedzialna za komunikację z RDBS&lt;/li&gt;
&lt;li&gt;pobrać można wartość tylko jednego pola z jednego rekordu&amp;nbsp;&lt;/li&gt;
&lt;li&gt;wybór rekordu odbywa się przez podanie wartości pola id&lt;/li&gt;
&lt;li&gt;pobranie/aktualizowanie wartości musi być możliwie proste&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Udało mi się uzyskać efekt, pozwalający pobrać wartość takim zapisem:&lt;/div&gt;&lt;code&gt;&lt;br /&gt;
$iAge = $db -&amp;gt; contact[17] -&amp;gt; age;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Aktualizacja wartości to z kolei:&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
$db -&amp;gt; contact[17] -&amp;gt; age = 18;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Przy czym:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;$db - to już istniejący obiekt zapewniający dostęp do bazy&lt;/li&gt;
&lt;li&gt;contact - nazwa tablicy w bazie&lt;/li&gt;
&lt;li&gt;17 - wartość pola id&lt;/li&gt;
&lt;li&gt;age - nazwa pola pobieranego/aktualizowanego&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Jak to uzyskałem?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Tytułem wstępu:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;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&lt;/li&gt;
&lt;li&gt;magiczna metoda __set() w klasie działa podobnie - pozwala ustawić wartość niedostępnej składowej. Otrzymuje dwa parametry - nazwę i nową wartość składowej&lt;/li&gt;
&lt;li&gt;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&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;A teraz - jak to działa?&lt;/div&gt;&lt;ul&gt;&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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&lt;/li&gt;
&lt;li&gt;na koniec zostajemy z obiektem klasy magdb_row. Otrzymał on w konstruktorze: obiekt &lt;b&gt;$db&lt;/b&gt; (pozwalający na dostęp do bazy), &lt;b&gt;nazwę tabeli&lt;/b&gt; z której pobieramy dane (ew. aktualizujemy), &lt;b&gt;id wiersza&lt;/b&gt; (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). &amp;nbsp;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ść&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;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.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Co dalej? To był tylko eksperyment, ale w ramach jego kontynuacji można by:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;obsłużyć offsetUnset() w magdb_table - pozwalając na usuwanie rekordów&lt;/li&gt;
&lt;li&gt;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&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-family: monospace;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;code&gt; class db{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function __get($name){&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;return new magdb_table($name, $this);&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class magdb_table implements ArrayAccess{&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;private $name;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;private $db;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function __construct($sName, $db){&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;$this -&amp;gt; name = $sName;&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;$this -&amp;gt; db = $db;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function offsetExists($offset){}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function offsetUnset($offset){}&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function offsetSet($offset, $value) {}&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function offsetGet($offset) {&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;return new magdb_row($this -&amp;gt; name, $offset, $this -&amp;gt; db);&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class magdb_row{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;private $table;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;private $rowId;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;private $db;&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function __construct($sTable, $iRowId, $db){&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;$this -&amp;gt; db = $db;&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;$this -&amp;gt; table = $sTable;&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;$this -&amp;gt; rowId = $iRowId;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function __get($sName){&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// sql goes here&amp;nbsp;&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;// select $sName from $this -&amp;gt; table where id = $this -&amp;gt; rowId&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $sName - column name&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $this -&amp;gt; table - table name&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $this -&amp;gt; rowId - id value&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;&amp;nbsp;public function __set($sName, $sValue){&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// sql goes here&amp;nbsp;&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;// update $this -&amp;gt; table set $sName where id = $this -&amp;gt; rowId&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $sName - column name&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $this -&amp;gt; table - table name&lt;br /&gt;
&amp;nbsp;&amp;nbsp; &amp;nbsp;// $this -&amp;gt; rowId - id value&lt;br /&gt;
&amp;nbsp;&amp;nbsp;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-3313149111782080286?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/oI-m3XvA4P0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/3313149111782080286/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2010/06/magia-w-php-nowy-artyku-po-reaktywacji.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/3313149111782080286?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/3313149111782080286?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/oI-m3XvA4P0/magia-w-php-nowy-artyku-po-reaktywacji.html" title="Magia w PHP - Nowy artykuł po reaktywacji bloga" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2010/06/magia-w-php-nowy-artyku-po-reaktywacji.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8NQHs7fCp7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-1363842504154838063</id><published>2008-09-17T00:07:00.002+02:00</published><updated>2010-06-14T00:08:11.504+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-14T00:08:11.504+02:00</app:edited><title>Łatwiejsze debugowanie z FirePHP</title><content type="html">&lt;h2&gt;Problem...&lt;/h2&gt;&lt;p&gt;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ż &lt;strong&gt;format zwracanych danych&lt;/strong&gt; uniemożliwia nam swobodne dodawanie własnego tekstu (XML, JSon no i oczywiście &lt;strong&gt;generowane dynamicznie grafiki&lt;/strong&gt;). Do tego dochodzi praca z Ajaksem - wyników pobranych przez XmlHTTPRequest nie widzimy...&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;... rozwiązany&lt;/h2&gt;&lt;p&gt;Skoro więc nie możemy przesłać dodatkowych komunikatów w wyniku, to może wykorzystać &lt;strong&gt;nagłówki HTTP&lt;/strong&gt;? Jak się okazuje, jest to nie tylko możliwe, ale otrzymujemy gotowe rozwiązanie w postaci dodatku do &lt;strong&gt;Firefoksa&lt;/strong&gt; - FirePHP i &lt;strong&gt;biblioteki PHP&lt;/strong&gt;. Strona projektu - &lt;a href="http://www.firephp.org/"&gt;firephp.org&lt;/a&gt;. Na stronie FirePHP dostępne są informacje, jak zintegrować bibliotekę z wieloma znanymi &lt;strong&gt;frameworkami&lt;/strong&gt; (CakePHP, CodeIgniter, Drupal, Kohana, ExpressionEngine, PRADO, Symfony, TYPO3, Zend Framework). &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Jak to działa na serwerze?&lt;/h2&gt;&lt;p&gt;Najpierw w kodzie wykorzystujemy bibliotekę FirePHP (wymagane PHP 5). Do dyspozycji mamy całą &lt;strong&gt;klasę FirePHP&lt;/strong&gt; lub bardzo wygodną funkcję &lt;strong&gt;fb()&lt;/strong&gt;. Najprostsze wywołanie to fb('Hello World'); ale można też bardziej skomplikowanie (np. wypluć całą tablicę lub obiekt) - &lt;a href="http://www.firephp.org/Wiki/Reference/Fb"&gt;użycie funkcji fb()&lt;/a&gt;. 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-&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Jak to działa w przeglądarce?&lt;/h2&gt;&lt;p&gt;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 - &lt;a href="http://getfirebug.com/"&gt;Firebug&lt;/a&gt;. 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.&lt;br /&gt;&lt;br /&gt;
&lt;img src='http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2008/09/simpleconsole.png' alt='Konsola Firebug z wynikami FirePHP' /&gt;&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Zalety&lt;/h2&gt;&lt;p&gt;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. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Wady&lt;/h2&gt;&lt;p&gt;Nie widzę... ;)&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Ciekawe co dalej... &lt;/h2&gt;&lt;p&gt;Dostępny jest patch do &lt;strong&gt;Apache Mod Rewrite&lt;/strong&gt;, wysyłający do FirePHP &lt;strong&gt;rewrite log&lt;/strong&gt;. Niestety patch został napisany przez niedoświadczonego programistę C i jest w stadium proof-of-concept. Miejmy nadzieję, że projekt się rozwinie.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Z informacji na stronie projektu wynika, że FirePHP może być także użyty w połączeniu z ASP, Pythonem czy środowiskiem Jaxer. &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;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.&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-1363842504154838063?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/GE_jKee386A" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/1363842504154838063/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2008/09/atwiejsze-debugowanie-z-firephp.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1363842504154838063?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1363842504154838063?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/GE_jKee386A/atwiejsze-debugowanie-z-firephp.html" title="Łatwiejsze debugowanie z FirePHP" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2008/09/atwiejsze-debugowanie-z-firephp.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEARHkzeip7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-6684169482450157293</id><published>2008-09-14T00:02:00.000+02:00</published><updated>2010-06-14T00:04:05.782+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-14T00:04:05.782+02:00</app:edited><title>Serwer w PHP</title><content type="html">&lt;p&gt;Dla wszystkich jest oczywiste, że PHP działa jako element serwera WWW. Jednak PHP może samo w sobie działać jako &lt;strong&gt;prosty serwer&lt;/strong&gt; (nie koniecznie HTTP). Dla takiego rozwiązania najlepiej wykorzystać wersję &lt;strong&gt;CLI PHP&lt;/strong&gt; (Command Line Interface)&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Dlaczego serwer w PHP?&lt;/h2&gt;&lt;p&gt;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ę &lt;strong&gt;komunikacja&lt;/strong&gt; 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 &lt;strong&gt;połączyć się przez TCP/IP&lt;/strong&gt;, wydać polecenie i odczytać wynik. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Jak to ma działać?&lt;/h2&gt;&lt;p&gt;Zasada działania jest prosta - skrypt PHP działający pod Windows (nazwijmy go "Sonda") &lt;strong&gt;nasłuchuje&lt;/strong&gt; 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.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Kod źródłowy&lt;/h2&gt;&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
set_time_limit(0);&lt;br /&gt;
$sckMain = socket_create(AF_INET, SOCK_STREAM, 0) or die();&lt;br /&gt;
socket_bind($sckMain, '192.168.1.101', 9876) or die();&lt;br /&gt;
socket_listen($sckMain) or die();&lt;br /&gt;
$oaClients = array();&lt;br /&gt;
while(true){&lt;br /&gt;
 $sckaRead = array();&lt;br /&gt;
 $sckaRead[0] = $sckMain;&lt;br /&gt;
 foreach($oaClients as $x =&gt; $oClient){ &lt;br /&gt;
  if($oClient -&gt; bClosed == true){&lt;br /&gt;
   unset($oaClients[$x]);&lt;br /&gt;
  }else{&lt;br /&gt;
   $sckaRead[] = $oClient -&gt; sckSocket;&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
 socket_select($sckaRead, $null = null, $null = null, 0);&lt;br /&gt;
 if(in_array($sckMain, $sckaRead)){&lt;br /&gt;
  $sckNewClient = socket_accept($sckMain);&lt;br /&gt;
  $oaClients[] = new clientHandler($sckNewClient);&lt;br /&gt;
 }&lt;br /&gt;
 foreach($oaClients as $oClient){&lt;br /&gt;
  if(in_array($oClient -&gt; sckSocket, $sckaRead)){&lt;br /&gt;
   $oClient -&gt; handle();&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
}&lt;br /&gt;
socket_close($sckMain);&lt;br /&gt;
class clientHandler{&lt;br /&gt;
 var $bClosed  = false;&lt;br /&gt;
 var $sckSocket  = '';&lt;br /&gt;
 var $bAuthenticated = false;&lt;br /&gt;
 function clientHandler($sckSocket){&lt;br /&gt;
  $this -&gt; sckSocket = $sckSocket;&lt;br /&gt;
  $this -&gt; send('HELLO');&lt;br /&gt;
 }&lt;br /&gt;
 function handle(){&lt;br /&gt;
  $sCommand = $this -&gt; read();&lt;br /&gt;
  list($sExecute, $sParameters) = explode(' ', $sCommand, 2);&lt;br /&gt;
  if($sCommand != ''){&lt;br /&gt;
   if($this -&gt; bAuthenticated == true){&lt;br /&gt;
    if($sCommand == 'logout'){&lt;br /&gt;
     $this -&gt; disconnect();&lt;br /&gt;
    }elseif($sExecute == 'echo'){&lt;br /&gt;
     $this -&gt; send($sParameters);&lt;br /&gt;
    }elseif($sExecute == 'time'){&lt;br /&gt;
     $this -&gt; send(date('Y-m-d H:i:s'));&lt;br /&gt;
    }else{&lt;br /&gt;
     $this -&gt; send('unknown command');&lt;br /&gt;
    }&lt;br /&gt;
   }elseif($sExecute == 'login'){&lt;br /&gt;
    if($sParameters == 'powiedz-przyjacielu-i-wejdz'){&lt;br /&gt;
     $this -&gt; bAuthenticated = true;&lt;br /&gt;
     $this -&gt; send('login ok');&lt;br /&gt;
    }else{&lt;br /&gt;
     $this -&gt; send('login error');&lt;br /&gt;
     $this -&gt; disconnect();&lt;br /&gt;
    }&lt;br /&gt;
   }else{&lt;br /&gt;
    $this -&gt; send('authenticate first');&lt;br /&gt;
   }&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
 function send($sMessage){&lt;br /&gt;
  socket_write($this -&gt; sckSocket, $sMessage . "\r\n" . chr(0));&lt;br /&gt;
 }&lt;br /&gt;
 function read(){&lt;br /&gt;
  return trim(@socket_read($this -&gt; sckSocket, 1024));&lt;br /&gt;
 }&lt;br /&gt;
 function disconnect(){&lt;br /&gt;
  $this -&gt; send('bye');&lt;br /&gt;
  $this -&gt; bClosed = true;&lt;br /&gt;
  socket_close($this -&gt; sckSocket);&lt;br /&gt;
 }&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Analiza&lt;/h2&gt;&lt;p&gt;Aby można było nawiązać wiele połączeń jednocześnie, stworzymy jedno "główne" &lt;strong&gt;gniazdo&lt;/strong&gt; (ang. &lt;strong&gt;socket&lt;/strong&gt;) przyjmujące nowe połączenia i przekazujące je do nowych gniazd obsługujących połączenia. Zaczynamy więc: &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
$sckMain = socket_create(AF_INET, SOCK_STREAM, 0) or die();&lt;br /&gt;
socket_bind($sckMain, '192.168.1.101', 9876) or die();&lt;br /&gt;
socket_listen($sckMain) or die();&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;&lt;strong&gt;socket_create()&lt;/strong&gt; tworzy nowe gniazdo, &lt;strong&gt;socket_bind()&lt;/strong&gt; łączy je z portem 9876 i adresem IP 192.168.1.101, &lt;strong&gt;socket_listen()&lt;/strong&gt; powoduje, że gniazdo zaczyna nasłuchiwać. Dodatkowo będziemy potrzebowali tablicy obiektów ($oaClients) obsługujących poszczególne połączenia.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Całość obsługi zamykamy w nieskończonej pętli (while(true)). Głównym jej elementem jest funkcja &lt;strong&gt;socket_select()&lt;/strong&gt;. 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 &lt;strong&gt;modyfikuje przekazane tablice&lt;/strong&gt; 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. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Przed wywołaniem socket_select() musimy więc przygotować sobie tablicę gniazd do monitorowania:&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
 $sckaRead = array();&lt;br /&gt;
 $sckaRead[0] = $sckMain;&lt;br /&gt;
 foreach($oaClients as $x =&gt; $oClient){&lt;br /&gt;
  if($oClient -&gt; bClosed == true){&lt;br /&gt;
   unset($oaClients[$x]);&lt;br /&gt;
  }else{&lt;br /&gt;
   $sckaRead[] = $oClient -&gt; sckSocket;&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;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.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
 if(in_array($sckMain, $sckaRead)){&lt;br /&gt;
  $sckNewClient = socket_accept($sckMain);&lt;br /&gt;
  $oaClients[] = new clientHandler($sckNewClient);&lt;br /&gt;
 }&lt;br /&gt;
 foreach($oaClients as $oClient){&lt;br /&gt;
  if(in_array($oClient -&gt; sckSocket, $sckaRead)){&lt;br /&gt;
   $oClient -&gt; handle();&lt;br /&gt;
  }&lt;br /&gt;
 }&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;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ę &lt;strong&gt;socket_accept()&lt;/strong&gt; która przyjmuje połączenie i zwraca nowe gniazdo obsługujące to połączenie. Tworzymy nowy obiekt klasy &lt;strong&gt;clientHandler&lt;/strong&gt; 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ę &lt;strong&gt;handle()&lt;/strong&gt; obiektu. Zadaniem tej metody jest przeczytanie "polecenia" z gniazda i odpowiedź na nie. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;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. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Na koniec - zagadka&lt;/h2&gt;&lt;p&gt;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"?&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-6684169482450157293?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/ePy15GzoeSQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/6684169482450157293/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2008/09/serwer-w-php.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/6684169482450157293?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/6684169482450157293?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/ePy15GzoeSQ/serwer-w-php.html" title="Serwer w PHP" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2008/09/serwer-w-php.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkEGRX4ycCp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-6845179159835777280</id><published>2008-03-30T00:14:00.000+01:00</published><updated>2010-06-27T00:17:04.098+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:17:04.098+02:00</app:edited><title>Szaf(k)a gra!</title><content type="html">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 &lt;strong&gt;wychodzą poza środowisko serwera web&lt;/strong&gt; i wchodzą w &lt;strong&gt;interakcję z otoczeniem&lt;/strong&gt; mniej lub bardziej "fizycznym".&lt;br /&gt;
&lt;br /&gt;
Najnowszym takim projektem - cały czas we wczesnej fazie dev ;) - jest domowa skrzynka muzyczna sterowana przez web. &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Jak to działa?&lt;/h2&gt;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). &lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Jak to jest zrobione?&lt;/h2&gt;Całkiem prosto. Oto lista potrzebnych rzeczy i softu:&lt;br /&gt;
&lt;ul&gt;  &lt;li&gt;komputer - 1 sztuka&lt;/li&gt;
  &lt;li&gt;kabel skrętka - w zależności od potrzeb, w moim przypadku 15 m&lt;/li&gt;
  &lt;li&gt;przedłużacz audio (mały jack) - w zależności od potrzeb&lt;/li&gt;
  &lt;li&gt;system operacyjny - Linux (ja użyłem dystrybucji Debian)&lt;/li&gt;
  &lt;li&gt;mpd (music player daemon) - soft grający, działa jako demon, nie ma interfejsu i sterowany jest przez sieć&lt;/li&gt;
  &lt;li&gt;mpc (konsolowy soft sterujący mpd)&lt;/li&gt;
  &lt;li&gt;mplayer - do odtwarzania stacji radiowych&lt;/li&gt;
  &lt;li&gt;serwer WWW (Apache) z obsługą PHP&lt;/li&gt;
  &lt;li&gt;konto na no-ip.org&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Krok I&lt;/h3&gt;- 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: &lt;a href="http://www.musicpd.org/"&gt;musicpd.org&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Krok II&lt;/h3&gt;- komputer wynosimy tam, gdzie będzie najmniej przeszkadzał, podłączamy do sieci, podłączamy głośniki. Rejestrujemy się na &lt;a href="http://www.no-ip.com/"&gt;no-ip.com&lt;/a&gt; i instalujemy klienta no-ip.com na maszynie&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Krok III&lt;/h3&gt;- 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 (&lt;a href="http://jquery.com/"&gt;jquery.com&lt;/a&gt; - ś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ę:&lt;br /&gt;
&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
class ncmpdcommander{&lt;br /&gt;
 var $fpStream;&lt;br /&gt;
&lt;br /&gt;
 function ncmpdcommander($sServer, $iPort){&lt;br /&gt;
  $this -&gt; fpStream = fsockopen($sServer, $iPort);&lt;br /&gt;
  //stream_set_blocking($this -&gt; fpStream, 0);&lt;br /&gt;
  $sResoponse = fread($this -&gt; fpStream, 1024);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 function disconnect(){&lt;br /&gt;
  fclose($this -&gt; fpStream);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 function doCommand($sCommand){&lt;br /&gt;
  fwrite($this -&gt; fpStream, $sCommand . "\n");&lt;br /&gt;
&lt;br /&gt;
  while(trim($sLine) != 'OK'){&lt;br /&gt;
   $sLine = fgets($this -&gt; fpStream, 512);&lt;br /&gt;
   if(trim($sLine) != 'OK'){&lt;br /&gt;
    $sResponse .= $sLine;&lt;br /&gt;
   }&lt;br /&gt;
  }&lt;br /&gt;
  return $sResponse;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 function playbackNext(){&lt;br /&gt;
  $this -&gt; doCommand('next');&lt;br /&gt;
 }&lt;br /&gt;
 function playbackPrev(){&lt;br /&gt;
  $this -&gt; doCommand('previous');&lt;br /&gt;
 }&lt;br /&gt;
 function playbackStop(){&lt;br /&gt;
  $this -&gt; doCommand('stop');&lt;br /&gt;
 }&lt;br /&gt;
 function playbackPlay(){&lt;br /&gt;
  $this -&gt; doCommand('play');&lt;br /&gt;
 }&lt;br /&gt;
 function playbackPause(){&lt;br /&gt;
  $this -&gt; doCommand('pause');&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 function getTitle(){&lt;br /&gt;
  $sStatus = $this -&gt; doCommand('currentsong');&lt;br /&gt;
  preg_match('!file: (.+)!', $sStatus, $shFound);&lt;br /&gt;
  $sTitle = $shFound[1];&lt;br /&gt;
  preg_match('!([^/]+)\.mp3$!', $sTitle, $shFound);&lt;br /&gt;
  $sTitle = $shFound[1];&lt;br /&gt;
  $sTitle = preg_replace('!^[0-9 _-]+!', '', $sTitle);&lt;br /&gt;
  $sTitle = str_replace('_', ' ', $sTitle);&lt;br /&gt;
&lt;br /&gt;
  return $sTitle;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 function getPlaylist(){&lt;br /&gt;
  $sPlaylist = $this -&gt; doCommand('playlist');&lt;br /&gt;
  $saPl = explode("\n", $sPlaylist);&lt;br /&gt;
  foreach($saPl as $sItem){&lt;br /&gt;
   list($iNum, $sTitle) = explode(':', $sItem, 2);&lt;br /&gt;
   $saPlaylist[$iNum] = $sTitle;&lt;br /&gt;
  }&lt;br /&gt;
  return $saPlaylist;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 function volumeUp(){&lt;br /&gt;
  $this -&gt; doCommand('volume +10');&lt;br /&gt;
 }&lt;br /&gt;
 function volumeDown(){&lt;br /&gt;
  $this -&gt; doCommand('volume -10');&lt;br /&gt;
 }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Samo wykorzystanie klasy jest już banalne:&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
require_once('ncmpdcommander.class.php');&lt;br /&gt;
&lt;br /&gt;
$oCmd = new ncmpdcommander('localhost', 6600);&lt;br /&gt;
switch($_GET['cmd']){&lt;br /&gt;
 case 'next':&lt;br /&gt;
   $oCmd -&gt; playbackNext();&lt;br /&gt;
  break;&lt;br /&gt;
 case 'prev':&lt;br /&gt;
   $oCmd -&gt; playbackPrev();&lt;br /&gt;
  break;&lt;br /&gt;
 case 'stop':&lt;br /&gt;
   $oCmd -&gt; playbackStop();&lt;br /&gt;
  break;&lt;br /&gt;
 case 'pause':&lt;br /&gt;
   $oCmd -&gt; playbackPause();&lt;br /&gt;
  break;&lt;br /&gt;
 case 'play':&lt;br /&gt;
   $oCmd -&gt; playbackPlay();&lt;br /&gt;
  break;&lt;br /&gt;
&lt;br /&gt;
 case 'get-title':&lt;br /&gt;
   echo $oCmd -&gt; getTitle();&lt;br /&gt;
  break;&lt;br /&gt;
&lt;br /&gt;
 case 'volume-up':&lt;br /&gt;
   $oCmd -&gt; volumeUp();&lt;br /&gt;
  break;&lt;br /&gt;
&lt;br /&gt;
 case 'volume-down':&lt;br /&gt;
   $oCmd -&gt; volumeDown();&lt;br /&gt;
  break;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
$oCmd -&gt; disconnect();&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;To już koniec&lt;/h3&gt;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ę!&lt;br /&gt;
&lt;br /&gt;
Pozdrawiam i do następnego wpisu!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-6845179159835777280?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/0S0Fp4s3dKY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/6845179159835777280/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2008/03/szafka-gra.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/6845179159835777280?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/6845179159835777280?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/0S0Fp4s3dKY/szafka-gra.html" title="Szaf(k)a gra!" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2008/03/szafka-gra.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYAR3s5cCp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-3248628644066235767</id><published>2007-09-16T00:07:00.000+02:00</published><updated>2010-06-27T00:09:06.528+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:09:06.528+02:00</app:edited><title>Domena z www - nie twórzmy niepotrzebnych ograniczeń</title><content type="html">&lt;h2&gt;Problem&lt;/h2&gt;&lt;p&gt;Po wpisaniu w Google zapytania &lt;strong&gt;jak wybrać domenę&lt;/strong&gt;, otrzymamy w przybliżeniu &lt;strong&gt;1,320,000&lt;/strong&gt; 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.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Dziwi mnie jednak, dlaczego tak wielu administratorów stron (adminów, webmasterów i im podobnych) &lt;strong&gt;nie zauważa problemu&lt;/strong&gt; subdomeny &lt;strong&gt;www&lt;/strong&gt;? Bardzo często spotykam się z sytuacją, gdy &lt;strong&gt;jedynym działającym&lt;/strong&gt; adresem strony jest &lt;strong&gt;www.example.org&lt;/strong&gt;, natomiast &lt;strong&gt;example.org&lt;/strong&gt; nie wskazuje na żaden serwer, albo - co gorsze - &lt;strong&gt;wskazuje na stronę firmy hostingowej&lt;/strong&gt;.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;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ć &lt;strong&gt;kolejne 4 znaki&lt;/strong&gt; - www. Złego wizerunku strony dopełnia fakt, że w gros przypadków nie ma żadnego uzasadnienia w wymuszaniu na użytkownikach dopisywania www.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Osobiście wpisując adresy stron, niemal zawsze &lt;strong&gt;pomijam www&lt;/strong&gt;. Często niestety kończy się to koniecznością ponownego wpisania adresu - tym razem z &lt;strong&gt;www&lt;/strong&gt;. Irytuje mnie to bardziej niż "strona przygotowana dla IE 5&amp;frac14;". Szczególnie, że rozwiązanie problemu jest niesamowicie banalne, a zaniechanie - w mojej opinii - wynika jedynie z ignorancji adminów.&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Rozwiązanie I (dobre, z wadami) - dajemy wybór internautom&lt;/h2&gt;&lt;p&gt;Dosyć dobrym rozwiązaniem jest takie skonfigurowanie sewera www, aby dla obu adresów (example.org i www.example.org) &lt;strong&gt;zwracał tę samą stronę&lt;/strong&gt;. W &lt;strong&gt;Apache&lt;/strong&gt; można np. skonfigurować dwa &lt;strong&gt;wirtualne hosty&lt;/strong&gt; wskazujące na to samo położenie dokumentów - &lt;strong&gt;DocumentRoot&lt;/strong&gt;.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Dzięki takiemu rozwiązaniu, obojętnie czy odwiedzający wpisze www.example.org czy example.org - zawsze zobaczy stronę której szuka.&lt;/p&gt;&lt;br /&gt;
&lt;h3&gt;Wady rozwiązania I &lt;/h3&gt;&lt;h4&gt;Internauta jest zdezorientowany&lt;/h4&gt;&lt;p&gt;Mniej doświadczeni internauci (np. ci, którzy na &lt;span style='font-weight: bold; color: #0000ff'&gt;e&lt;/span&gt; mówią "Internet") mogą czuć sie zdezorientowani, a nawet zagrożeni tym, że jakaś strona ma adres bez www na początku.&lt;/p&gt;&lt;p&gt;Możliwe rozwiązania: wykorzystać Rozwiązanie II lub zignorować problem ;)&lt;/p&gt;&lt;br /&gt;
&lt;h4&gt;Przeglądarka jest zdezorientowana&lt;/h4&gt;&lt;p&gt;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:&lt;br /&gt;
&lt;/p&gt;&lt;code&gt;&lt;br /&gt;
setcookie('kruche', 'z_cukrem');&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;p&gt;to nie dziwmy się, że użytkownik który np. zalogował się na example.org/login/, &lt;strong&gt;nie będzie zalogowany&lt;/strong&gt; na www.example.org/.&lt;/p&gt;&lt;p&gt;Możliwe rozwiązania: ustawiajmy ciasteczka korzystając z opcjonalnych parametrów setcookie:&lt;br /&gt;
&lt;/p&gt;&lt;code&gt;&lt;br /&gt;
&lt;pre style='width: 100%; overflow: scroll; padding: 10px 5px;'&gt;bool setcookie ( string $name [, string $value [, int $expire [, string $path [, &lt;strong&gt;string $domain&lt;/strong&gt; [, bool $secure [, bool $httponly]]]]]])
&lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;
&lt;p&gt;Czyli możemy napisać tak:&lt;/p&gt;&lt;code&gt;&lt;br /&gt;
&lt;pre style='width: 100%; overflow: scroll; padding: 10px 5px;'&gt;setcookie('kruche', 'z_cukrem', time() + 60 * 60 * 24 * 31, '/', '.example.org');
&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;
&lt;p&gt;ciasteczko będzie widoczne dla &lt;strong&gt;wszystkich subdomen&lt;/strong&gt; naszego serwisu.&lt;/p&gt;&lt;p&gt;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 &lt;strong&gt;zawsze na adres www.example.org&lt;/strong&gt;. Wiadomo jaki jest efekt: gdy loguję się na example.org - zamiast potwierdzenia płatności, otrzymuję ekran logowania...&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;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/&lt;/p&gt;&lt;p&gt;Możliwe rozwiązania: Rozwiązanie II&lt;/p&gt;&lt;br /&gt;
&lt;h4&gt;serwer jest zdezorientowany&lt;/h4&gt;&lt;p&gt;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).&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Możliwe rozwiązanie: Rozwiązanie II&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Rozwiązanie II (dobre) - odbieramy wybór internautom&lt;/h2&gt;&lt;p&gt;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 &lt;strong&gt;ustawienie przekierowania&lt;/strong&gt; na jednym z adresów. Takie rozwiązanie możemy zrealizować na dwa sposoby:&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h3&gt;Apache i mod_rewrite&lt;/h3&gt;&lt;p&gt;Używamy &lt;strong&gt;mod_rewrite&lt;/strong&gt; w Apache aby przekierować użytkownika z example.org na www.example.org. Można to zrobić np. tak:&lt;br /&gt;
&lt;/p&gt;&lt;code style='overflow: scroll;'&gt;&lt;br /&gt;
&lt;pre style='width: 100%; overflow: scroll; padding: 10px 5px;'&gt;&amp;lt;VirtualHost 127.0.0.1&amp;gt;
ServerName example.org
DocumentRoot /var/www/i/co/tam/jeszcze_chcecie
RewriteEngine On
RewriteRule ^/(.*)?$ http://www.example.org/$1?%{QUERY_STRING} [R=301,L]
&amp;lt;/VirtualHost&amp;gt;
&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;p&gt;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.&lt;/p&gt;&lt;br /&gt;
&lt;h3&gt;PHP i header()&lt;/h3&gt;&lt;p&gt;Jeżeli nie macie możliwości modyfikowania konfiguracji Apache, podobne rozwiązanie można uzyskać korzystając z PHP i funkcji header():&lt;br /&gt;
&lt;/p&gt;&lt;code&gt;&lt;br /&gt;
&lt;pre style='width: 100%; overflow: scroll; padding: 10px 5px;'&gt;if($_SERVER['HTTP_HOST'] == 'example.org'){
header("Location: http://www.example.org{$_SERVER['REQUEST_URI']}");
die();
}
&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;i po problemie!&lt;/h2&gt;&lt;p&gt;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.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Apel dnia ;)&lt;/h2&gt;&lt;p&gt;Nie narzucajmy sztucznych i niepotrzebnych ograniczeń!&lt;/p&gt;&lt;p&gt;Takim niepotrzebnym ograniczeniem jest właśnie wymaganie wpisywania www lub to o którym pisał kiedyś Robert Drózd: &lt;a href="http://www.webaudit.pl/blog/2006/za-krotkie-zle-za-dlugie-tez-zle/"&gt;Za krótkie - źle, za długie - też źle&lt;/a&gt;.&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-3248628644066235767?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/0zqzVCk2Ihk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/3248628644066235767/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2007/09/domena-z-www-nie-tworzmy-niepotrzebnych.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/3248628644066235767?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/3248628644066235767?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/0zqzVCk2Ihk/domena-z-www-nie-tworzmy-niepotrzebnych.html" title="Domena z www - nie twórzmy niepotrzebnych ograniczeń" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2007/09/domena-z-www-nie-tworzmy-niepotrzebnych.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMNRXg8fip7ImA9WxFVE04.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-1318697423487972320</id><published>2007-09-13T12:10:00.000+02:00</published><updated>2010-06-12T12:11:34.676+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-12T12:11:34.676+02:00</app:edited><title>2^8 dzień roku</title><content type="html">&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;W 2&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;&lt;sup&gt;8&lt;/sup&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;dniu roku, wszystkim programistom życzę wszystkiego najlepszego z okazji&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;&lt;strong&gt;&lt;a href="http://pl.wikipedia.org/wiki/Dzie%C5%84_programisty" style="color: #0066cc; text-decoration: none;"&gt;Dnia Programisty&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #333333; font-family: 'Lucida Grande', Verdana, Arial, sans-serif; font-size: 12px; line-height: 16px;"&gt;. Ciekawy jestem, jak go obchodzicie…&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-1318697423487972320?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/_XoObwvfz7k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/1318697423487972320/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2007/09/28-dzien-roku.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1318697423487972320?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1318697423487972320?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/_XoObwvfz7k/28-dzien-roku.html" title="2^8 dzień roku" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2007/09/28-dzien-roku.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMBQ34yeip7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-843689745996009460</id><published>2006-12-03T23:59:00.001+01:00</published><updated>2010-06-14T00:00:52.092+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-14T00:00:52.092+02:00</app:edited><title>Czyżby TODO idealne?</title><content type="html">&lt;h3&gt;TODO: znaleźć TODO&lt;/h3&gt;Od bardzo długiego czasu, poszukiwałem programu, który pomógłby mi zarządzać listą zadań do zrobienia - &lt;strong&gt;TODO&lt;/strong&gt;. 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 &lt;strong&gt;wymagały&lt;/strong&gt; od użytkownika podawania wszystkich istnejących parametrów zadań. &lt;br /&gt;
&lt;br /&gt;
Jednym z bardziej złożonych systemów, był &lt;a href="http://dev.mysql.com/downloads/other/eventum/"&gt;Eventum&lt;/a&gt;, udostępniony przez &lt;strong&gt;MySQL AB&lt;/strong&gt;. 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. &lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;DONE: znaleźć TODO&lt;/h3&gt;W końcu, dzięki - o ile dobrze pamiętam - serwisowi &lt;a href="http://www.dzone.com"&gt;dzone.com&lt;/a&gt; - znalazłem program, który ma szansę zostać moim &lt;strong&gt;TODO idealnym&lt;/strong&gt;. Jest to &lt;strong&gt;&lt;a href="http://www.abstractspoon.com/tdl_resources.html"&gt;ToDoList&lt;/a&gt;&lt;/strong&gt; (obecnie w wersji 5.0.1) udostępniony przez &lt;a href="http://www.abstractspoon.com/"&gt;AbstractSpoon Software&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;ToDoList&lt;/h4&gt;Główne okno programu zawiera:&lt;br /&gt;
&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2007/01/todo-overview.png" title="Todo - okno główne"&gt;&lt;img id="image77" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2007/01/todo-overview.thumbnail.png" alt="Todo - okno główne" height="96" width="119" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;listę zadań w postaci drzewa&lt;/li&gt;
 &lt;li&gt;panel z parametrami aktualnie wybranej pozycji&lt;/li&gt;
 &lt;li&gt;pole komentarza (RTF)&lt;/li&gt;
 &lt;li&gt;panel wyszukiwania&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Dla każdego z zadań, można zdefiniować parametry:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;priorytet (0-10)&lt;/li&gt;
 &lt;li&gt;ryzyko (0-10)&lt;/li&gt;
 &lt;li&gt;procent wykonania&lt;/li&gt;
 &lt;li&gt;przewidywany czas potrzebny na wykonanie&lt;/li&gt;
 &lt;li&gt;rzeczywisty czas poświęcony na wykonanie&lt;/li&gt;
 &lt;li&gt;datę rozpoczęcia pracy&lt;/li&gt;
 &lt;li&gt;datę rzeczywistego zakończenia pracy&lt;/li&gt;
 &lt;li&gt;termin wykonania&lt;/li&gt;
 &lt;li&gt;osobę, której przypisano zadanie&lt;/li&gt;
 &lt;li&gt;osobę, ktora przypisała zadanie&lt;/li&gt;
 &lt;li&gt;status (dowolnie definiowana lista)&lt;/li&gt;
 &lt;li&gt;kategoria (dowolnie definiowana lista)&lt;/li&gt;
 &lt;li&gt;zewnętrzne ID&lt;/li&gt;
 &lt;li&gt;koszt&lt;/li&gt;
 &lt;li&gt;zależność od innego zadania&lt;/li&gt;
 &lt;li&gt;kolor&lt;/li&gt;
 &lt;li&gt;komentarz&lt;/li&gt;
&lt;/ul&gt;Dodatkowo, każde z zadań można &lt;strong&gt;oznaczyć flagą&lt;/strong&gt;, oraz przypisać do niego plik. &lt;br /&gt;
Bardzo ważną zaletą programu jest to, że wszystkie parametry zadania są &lt;strong&gt;opcjonalne&lt;/strong&gt;. 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ć. &lt;br /&gt;
&lt;br /&gt;
Wszystkie parametry zadania definiujemy w panelu znajdującym się pod listą:&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/szczegoly.png" title="szczegóły zadania"&gt;&lt;img id="image73" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/szczegoly.thumbnail.png" alt="szczegóły zadania" height="17" width="128" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Zadania można organizować w hierarchiczne drzewo z wieloma poziomami zagłębienia.&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/zadania.png" title="lista zadań"&gt;&lt;img id="image75" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/zadania.thumbnail.png" alt="lista zadań" height="42" width="128" /&gt;&lt;/a&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Kilka parametrów zadań zasługuje na dodatkowe kilka zdań:&lt;br /&gt;
&lt;dl&gt;&lt;dt&gt;priorytet&lt;/dt&gt;
&lt;dd&gt;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)&lt;/dd&gt;

&lt;dt&gt;procent wykonania&lt;/dt&gt;
&lt;dd&gt;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.&lt;/dd&gt;

&lt;dt&gt;rzeczywisty czas poświęcony na wykonanie&lt;/dt&gt;
&lt;dd&gt;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).&lt;/dd&gt;

&lt;dt&gt;status/kategoria&lt;/dt&gt;
&lt;dd&gt;można przypisać dowolny status/kategorię. Wcześniej wprowadzone wartości tworzą listę, z której można szybko wybrać status/kategorię.&lt;/dd&gt;

&lt;dt&gt;zależność&lt;/dt&gt;
&lt;dd&gt;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.&lt;/dd&gt;

&lt;dt&gt;komentarz&lt;/dt&gt;
&lt;dd&gt;jest edytowany w polu RTF, pozwalającym na formatowanie tekstu, a także na wstawianie odnośników do innych zadań, w postaci &lt;code&gt;tdl://xxx&lt;/code&gt; gdzie xxx jest identyfikatorem zadania. Odnośnik może prowadzić także do zadań zdefiniowanych w innych plikach. Pole komentarza wygląda tak: &lt;br /&gt;&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/komentarze.png" title="komentarze"&gt;&lt;img id="image72" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/komentarze.thumbnail.png" alt="komentarze" height="96" width="108" /&gt;&lt;/a&gt;&lt;/dd&gt;

&lt;/dl&gt;&lt;br /&gt;
&lt;h5&gt;Praca zespołowa&lt;/h5&gt;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: &lt;br /&gt;&lt;br /&gt;
&lt;a id="p71" rel="attachment" class="imagelink" href="http://www.netcoffee.pl/pogodzinach/2006/12/03/czyzby_todo_idealne/tryb-blokowania/" title="Tryb blokowania"&gt;&lt;img id="image71" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/11/klodka.png" alt="Tryb blokowania" height="57" width="61" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
Wcześniej w opcjach trzeba włączyć opcję "&lt;strong&gt;Enable simple source control&lt;/strong&gt;". Pracę kilku osób ułatwiają dodatkowe opcje:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;automatyczne ponawianie próby zablokowania pliku do edycji, w przypadku gdy edycję zablokował inny użytkownik&lt;/li&gt;
 &lt;li&gt;automatyczne odblokowywanie edycji przy zamykaniu listy&lt;/li&gt;
 &lt;li&gt;odblokowywanie listy, gdy nie dokonano zmian przez ustalony czas&lt;/li&gt;
 &lt;li&gt;automatyczne sprawdzanie statusu listy co ustalony czas i automatyczne wczytywanie aktualnego pliku&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h5&gt;Dodatkowe opcje&lt;/h5&gt;W ustawieniach można zdefiniować &lt;strong&gt;globalny skrót klawiaturowy&lt;/strong&gt; przywołujący i minimalizujący program, który może minimalizować się do &lt;strong&gt;ikony systemowej&lt;/strong&gt;. 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).&lt;br /&gt;
&lt;br /&gt;
Warto także wspomnieć o tym, że:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;istnieje możliwości eksportu/importu list zadań (HTML, MLO, Outline, GanttProject, iCalendar, ...)&lt;/li&gt;
 &lt;li&gt;istnieje możliwości dodawania pluginów i narzędzi zewnętrznych (tu można wykorzystać parametr &lt;strong&gt;external ID&lt;/strong&gt;)&lt;/li&gt;
 &lt;li&gt;lista jest przechowywana jako plik &lt;strong&gt;XML&lt;/strong&gt;, można więc z łatwością stworzyć własne narzędzie potrafiące odczytywać plik&lt;/li&gt;
 &lt;li&gt;można w samym programie przetwarzać listę przy użyciu &lt;strong&gt;szablonu XSLT&lt;/strong&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;
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?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-843689745996009460?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/CeB0d1LoBzU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/843689745996009460/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/12/czyzby-todo-idealne.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/843689745996009460?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/843689745996009460?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/CeB0d1LoBzU/czyzby-todo-idealne.html" title="Czyżby TODO idealne?" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/12/czyzby-todo-idealne.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkAER38ycSp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-4781644097775203893</id><published>2006-09-18T00:17:00.000+02:00</published><updated>2010-06-27T00:18:26.199+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:18:26.199+02:00</app:edited><title>Launchy - szybkie uruchamianie</title><content type="html">&lt;a href="http://www.launchy.net"&gt;Launchy&lt;/a&gt;, od kiedy zainstalowałem go pierwszy raz, stał się narzędziem, bez któego trudno mi jest się obejść. Dzięki temu małemu programowi zapomnisz o menu start i skrótach porozrzucanych po pulpicie i na pasku zadań.&lt;br /&gt;
&lt;br /&gt;
Po zainstalowaniu programu, działa on w tle. Po wciśnięciu skrótu ALT + SPACJA (skrót można zmienić), na ekranie pojawia się przyjemne w wyglądzie okienko programu: &lt;br /&gt;
&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/09/launchy.png" title="Launchy - okno programu"&gt;&lt;img id="image66" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/09/launchy.thumbnail.png" alt="Launchy - okno programu" height="65" width="128"/&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;br /&gt;
Po pojawieniu się okienka możemy zacząć wpisywać &lt;strong&gt;nazwę programu&lt;/strong&gt; lub &lt;strong&gt;pliku&lt;/strong&gt;, który chcemy otworzyć. Domyślnie Launchy &lt;strong&gt;indeksuje&lt;/strong&gt; Menu Start. Gdy wpisywany tekst będzie pasował do nazwy przynajmniej jednego zindeksowanego pliku, Launchy wyświetli propozycje na rozwijalnej liście, z której można wybrać program/plik który chcemy uruchomić/otworzyć.&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Konfigurując&lt;/strong&gt; program, możemy zdefiniować, które &lt;strong&gt;katalogi i pliki z jakimi rozszerzeniami&lt;/strong&gt; należy indeksować. Dzięki temu możemy uruchamiać nie tylko wszystkie programy, ale także np. pliki &lt;strong&gt;MP3&lt;/strong&gt; w domyślnym programie. &lt;br /&gt;
&lt;br /&gt;
Samo &lt;strong&gt;dopasowywanie wpisanej nazwy&lt;/strong&gt; do nazw w indeksie jest bardzo &lt;strong&gt;dopracowane i elastyczne&lt;/strong&gt;. Dzięki temu możemy wpisać tylko fragment nazwy pliku/programu. Co więcej Launchy jest odporny na literówki i łatwo domyśla się, jaki program chcemy uruchomić. &lt;br /&gt;
&lt;br /&gt;
Naprawdę polecam!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-4781644097775203893?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/MBL937bIhnU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/4781644097775203893/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/09/launchy-szybkie-uruchamianie.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4781644097775203893?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4781644097775203893?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/MBL937bIhnU/launchy-szybkie-uruchamianie.html" title="Launchy - szybkie uruchamianie" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/09/launchy-szybkie-uruchamianie.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08FQHk6fCp7ImA9WxFUFUU.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-1249230071099750634</id><published>2006-08-02T00:02:00.000+02:00</published><updated>2010-06-27T00:03:31.714+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:03:31.714+02:00</app:edited><title>mod_security: testowanie konfiguracji</title><content type="html">&lt;strong&gt;mod_security&lt;/strong&gt; to moduł serwera &lt;strong&gt;Apache&lt;/strong&gt; będący systemem &lt;strong&gt;wykrywania włamań&lt;/strong&gt;. mod_security analizuje przychodzące dane (metody GET i POST a także cookies) a także dane &lt;strong&gt;odsyłane do klienta&lt;/strong&gt;. Po wykryciu zdefiniowanych w konfiguracji fraz (np. zapytań sql, kodu html, kodu javascript), mod_security wykonuje jedną ze zdefiniowanych &lt;strong&gt;akcji&lt;/strong&gt;. Może to być np. wysłanie do klienta kodu błędu, przekierowanie klienta, &lt;strong&gt;wstrzymanie odpowiedzi&lt;/strong&gt; na ustaloną ilość milisekund (przydatne w &lt;strong&gt;walce z botami&lt;/strong&gt;) lub zablokowanie rządania. Tyle tytułem wstępu.&lt;br /&gt;
&lt;br /&gt;
Sam moduł może być dla aplikacji którą &lt;strong&gt;chroni&lt;/strong&gt; tyle zbawienny, co &lt;strong&gt;niebezpieczny&lt;/strong&gt;. Zastosowanie zbyt restrykcyjnych reguł filtrowania może doprowadzić aplikację do &lt;strong&gt;stanu nieużywalności&lt;/strong&gt;. Dlatego warto już na etapie tworzenia aplikacji skonfigurować moduł i testować konfigurację, na samym początku jako akcje podejmowane po wykryciu potencjalnie niebezpiecznych danych ustawić jedynie &lt;strong&gt;logowanie zdarzeń&lt;/strong&gt;. W trakcie pracy można przeglądać logi serwera i korygować ustawienia mod_security lub zmieniać działanie aplikacji.&lt;br /&gt;
&lt;br /&gt;
Innym, wygodniejszym sposobem wykrywania konfiliktów między konfiguracją mod_security a wymaganiami aplikacji (np. gdy w systemie CMS edytujemy kod html strony, mod_security nie powinien podejmować żadnych działań), jest poniższy kod PHP wklejony w sekcji BODY dokumentu HTML:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
&lt;pre style='width: 100%; overflow: scroll; padding: 10px 5px;'&gt;&amp;lt;?php
if(isset($_SERVER['HTTP_MOD_SECURITY_MESSAGE']) 
  and $_SERVER['REMOTE_ADDR'] == '192.168.0.32'){?&amp;gt;
 &amp;lt;img style='position: fixed; top: 10px; right: 10px;' 
  src='style/error.png' 
  alt='' 
  title='&amp;lt;?php echo htmlspecialchars(stripslashes($_SERVER['HTTP_MOD_SECURITY_MESSAGE'])) ?&amp;gt;'
 /&gt;
&amp;lt;?php } ?&amp;gt;
&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Rozwiązanie to bazuje na tym, że po wykryciu potencjalnie niebezpiecznych danych, mod_security ustawia &lt;strong&gt;zmienną systemową&lt;/strong&gt;, której zawartość możemy odczytać w zmiennej &lt;strong&gt;&lt;code&gt;$_SERVER['HTTP_MOD_SECURITY_MESSAGE']&lt;/code&gt;&lt;/strong&gt;. W warunku sprawdzamy, czy ta zmienna jest ustawiona i czy rządanie nadeszło z komputera admina (ten warunek możemy oczywiście dostosować do naszych wymagań i sprawdzać np. czy zalogowany jest administrator itp). Jeżeli tak, do strony dodajemy mały obrazek, który &lt;strong&gt;prawdziwe przeglądarki&lt;/strong&gt; wyświetlą zawsze w górnym prawym rogu. Jako tytuł obrazka wstawiona jest treść błędu zgłoszonego przez mod_security.&lt;br /&gt;
&lt;br /&gt;
Dzięki takiemu rozwiązaniu, już podczas tworzenia, testowania lub w trakcie normalnej, codziennej pracy z aplikacją, na bieżąco będziemy wiedzieli, czy wprowadzone dane spowodują błąd zabezpieczeń.&lt;br /&gt;
&lt;br /&gt;
Oczywiście to proste rozwiązanie jedynie ułatwi testowanie aplikacji - nadal konieczne będzie sprawdzanie logów serwera.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Polecam uwadze&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.modsecurity.org"&gt;strona modułu &lt;strong&gt;mod_security&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-1249230071099750634?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/SBjJ3e7vDSE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/1249230071099750634/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/08/modsecurity-testowanie-konfiguracji.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1249230071099750634?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1249230071099750634?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/SBjJ3e7vDSE/modsecurity-testowanie-konfiguracji.html" title="mod_security: testowanie konfiguracji" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/08/modsecurity-testowanie-konfiguracji.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0AHQno-fip7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-8356425822570152164</id><published>2006-04-17T23:47:00.000+02:00</published><updated>2010-06-13T23:48:53.456+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-13T23:48:53.456+02:00</app:edited><title>Zarządanie relacjami w phpMyAdmin</title><content type="html">&lt;p&gt;Jedną z mniej znanych funkcji &lt;a href="http://www.phpmyadmin.net"&gt;phpMyAdmin&lt;/a&gt; jest &lt;strong&gt;zarządzanie relacjami&lt;/strong&gt; w bazie danych. Na małą popularność tej opcji wpływa konieczność przeprowadzenia dodaktowej konfiguracji aby była ona widoczna.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;phpMyAdmin informacje o relacjach między tabelami przechowuje w tabelach w zdefiniowanej bazie danych. Dlatego najlepiej jest utworzyć &lt;strong&gt;oddzielną bazę danych&lt;/strong&gt;, tylko na potrzeby phpMyAdmin. Domyślna nazwa bazy to &lt;strong&gt;phpmyadmin&lt;/strong&gt;. &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Aby utworzyć tabele niezbędne do dzialania zaawansowanych funkcji phpMyAdmin, wystarczy uruchomić instrukcje SQL znajdujące się w pliku &lt;code&gt;scripts/create_tables.sql&lt;/code&gt; w archiwum z phpMyAdmin. Skrypt ten zakłada, że istnieje użytkownik &lt;strong&gt;pma&lt;/strong&gt;, z poziomu którego będzie odbywało się zarządanie danymi. &lt;/p&gt;&lt;p&gt;Skrypt:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;usuwa&lt;/strong&gt; bazę phpmyadmin (jeżeli istnieje)&lt;/li&gt;
&lt;li&gt;zakłada ją ponownie&lt;/li&gt;
&lt;li&gt;nadaje prawa do wykonywania SELECT, INSERT, UPDATE i DELETE użytkownikowi pma w bazie phpmyadmin&lt;/li&gt;
&lt;li&gt;tworzy potrzebne tabele&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Kolejnym krokiem jest uaktualnienie pliku &lt;code&gt;config.inc.php&lt;/code&gt;. Dla każdego z używanych serwerów, należy podać wartość zmiennych:&lt;br /&gt;
&lt;/p&gt;&lt;pre&gt;# nazwa bazy danych phpMyAdmin
$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';

# nazwa tabeli z opisem relacji
$cfg['Servers'][$i]['relation'] = 'pma_relation';
&lt;/pre&gt;&lt;br /&gt;
&lt;p&gt;Po przeprowadzeniu tych operacji, w interfejsie phpMyAdmin zobaczymy nową opcję: &lt;strong&gt;Widok relacyjny&lt;/strong&gt; w widoku &lt;strong&gt;struktury tabeli&lt;/strong&gt;: &lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel01.png" title="Nowa opcja: widok relacyjny"&gt;&lt;img id="image58" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel01.thumbnail.png" alt="Nowa opcja: widok relacyjny" height="96" width="95"/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Po wybraniu opcji "Widok relacyjny" otrzymujemy dostęp do formularza, w którym możemy &lt;strong&gt;zdefiniować relacje&lt;/strong&gt; między tabelami:&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel02.png" title="Definiowanie relacji między tabelami"&gt;&lt;img id="image59" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel02.thumbnail.png" alt="Definiowanie relacji między tabelami" height="96" width="112"/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Dla każdego pola tabeli, możemy zdefiniować, któremu polu w innej tabeli odpowiada. Robimy to wybierając odpowiednie pole z listy rozwijalnej. Dodatkowo, dla każdej tabeli możemy wybrać, zawartość którego pola będzie pokazywana jako etykieta rekordu. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Dla przykładu rozważmy takie dwie tabele, zawierające dane menedżera projektów.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Tabela project&lt;/p&gt;&lt;ul&gt;&lt;li&gt;id - int(10)&lt;/li&gt;
&lt;li&gt;name - char(100)&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;p&gt;Tabela item&lt;/p&gt;&lt;ul&gt;&lt;li&gt;id - int(10)&lt;/li&gt;
&lt;li&gt;project_id - int(10)&lt;/li&gt;
&lt;li&gt;name - char(100)&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;p&gt;W tabeli 'project' przechowujemy nazwy projektów, w tabeli 'item' - nazwy poszczególnych elementów projektów. Każdy 'item' ma przypisany także id projektu, do którego należy. Po wejściu do widoku relacyjnego tabeli 'item', dla pola 'project_id' należy wybrać pozycję 'project -&amp;gt; id' (co oznacza pole 'id' w tabeli 'project'). &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;W widoku relacyjnym tabeli 'project' wybieramy wyświetlane pole: 'name'. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Dzięki temu, podczas edycji rekordu z tabeli 'item', zamiast pola tekstowego, dla 'id' zobaczymy listę rozwijalną, z której będzie można wybrać odpowiedni projekt: &lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel03.png" title="Formularz edycji danych"&gt;&lt;img id="image60" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel03.thumbnail.png" alt="Formularz edycji danych" height="34" width="128"/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;W widoku tabeli danych, zawartość pól w kolumnie 'project_id' będzie odnośnikiem:&lt;br /&gt;
&lt;a class="imagelink" href="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel04.png" title="Widok tabeli danych"&gt;&lt;img id="image61" src="http://www.netcoffee.pl/pogodzinach/wp-content/uploads/2006/04/pma_rel04.thumbnail.png" alt="Widok tabeli danych" height="15" width="128"/&gt;&lt;/a&gt;&lt;br /&gt;
Po jego kliknięciu zobaczymy szczegóły odpowiedniego projektu.&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-8356425822570152164?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/cDeK9qxMpkc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/8356425822570152164/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/04/zarzadanie-relacjami-w-phpmyadmin.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8356425822570152164?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8356425822570152164?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/cDeK9qxMpkc/zarzadanie-relacjami-w-phpmyadmin.html" title="Zarządanie relacjami w phpMyAdmin" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/04/zarzadanie-relacjami-w-phpmyadmin.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMEQ3w6fCp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-8441667206104037217</id><published>2006-02-13T00:12:00.000+01:00</published><updated>2010-06-27T00:13:22.214+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:13:22.214+02:00</app:edited><title>Przykładowa konfiguracja serwera Apache</title><content type="html">&lt;p&gt;Przeglądając ostatnio archiwa dokumentów na dysku, natknąłem się na starą pracę zaliczeniową (z przedmiotu którego nazwy już nie pamiętam ;) ). Opisałem w niej przykładową konfigurację serwera Apache. &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Poruszane zagadnienia obejmują:&lt;br /&gt;
&lt;/p&gt;&lt;ul&gt; &lt;li&gt;serwery wirtualne&lt;/li&gt;
 &lt;li&gt;logowanie&lt;/li&gt; 
 &lt;li&gt;pliki własnej konfiguracj - .htaccess&lt;/li&gt;
 &lt;li&gt;własne strony błędów&lt;/li&gt;
 &lt;li&gt;strony użytkowników&lt;/li&gt;
 &lt;li&gt;negocjacja zawartości&lt;/li&gt;
 &lt;li&gt;uwierzytelnianie&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Cała opisana w dokumencie konfiguracja oczywiście działała na testowej instalacji Apache pod Windows 2000. &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Doszedłem do wniosku, że mimo braków samego opracowania, warto udostępnić dokument szerszej publiczności. Co prawda powstał on ponad dwa lata temu i nigdy nie pretendował do miana "Wprowadzenia do Apache", myślę jednak, że może przydać się nie jednemu początkującemu użytkownikowi Apache. &lt;br /&gt;
&lt;/p&gt;&lt;p style='border: 1px solid #e6e6e6; background-color: #f2f2f2; text-align: center; font-weight: bold; padding: 10px; margin: 5px 20px;'&gt;&lt;a href="http://download.netcoffee.pl/apache_www.pdf"&gt;Konfiguracja Apache - PDF 158kB&lt;/a&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Dokument jest udostępniony na licencji &lt;a href="http://creativecommons.org/licenses/by-nc-sa/2.0/pl/"&gt;Creative Commons - BY-NC-SA&lt;/a&gt;. Nie biorę na siebie żadnej odpowiedzialności za skutki wykorzystania informacji zawartych dokumencie.&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-8441667206104037217?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/ePHx5kFFnE4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/8441667206104037217/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/02/przykadowa-konfiguracja-serwera-apache.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8441667206104037217?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8441667206104037217?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/ePHx5kFFnE4/przykadowa-konfiguracja-serwera-apache.html" title="Przykładowa konfiguracja serwera Apache" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/02/przykadowa-konfiguracja-serwera-apache.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04NSXgycSp7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-4982051803295691326</id><published>2006-02-11T23:51:00.001+01:00</published><updated>2010-06-13T23:53:18.699+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-13T23:53:18.699+02:00</app:edited><title>Kontrola wydajności - Apache Benchmark</title><content type="html">&lt;p&gt;Niejednokrotnie wprowadzamy rozwiązania, co do wydajności których nie możemy być pewni. Warto jest wówczas skorzystać z narzędzia pozwalającego na jej kontrolę. Moim ulubionym narzędziem jest &lt;strong&gt;Apache Benchmark (ab.exe)&lt;/strong&gt;, dostępny razem z dystrybucją serwera Apache.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;AB (Apache Benchmark) pozwala na wielokrotne wywołanie URL i przygotowuje statystyki czasu wykonania.&lt;br /&gt;
Program wywołujemy z &lt;strong&gt;linii poleceń&lt;/strong&gt;, jego najważniejsze parametry to:&lt;br /&gt;
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;n&lt;/em&gt; - ilość zapytań&lt;/li&gt;
&lt;li&gt;&lt;em&gt;c&lt;/em&gt; - ilość zapytań w tym samym czasie&lt;/li&gt;
&lt;li&gt;&lt;em&gt;k&lt;/em&gt; - wymusza użycie stałego połączenia - &lt;em&gt;HTTP KeepAlive&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Przykładowe wywołanie może mieć postać:&lt;br /&gt;
&lt;/p&gt;&lt;pre&gt;ab.exe -n100 -c100 -k http://www.example.org/ab_test.php&lt;/pre&gt;&lt;p&gt;Spowoduje ono pobranie pliku ab_test.php z serwera www.example.org 100 razy, przy czym w jednym czasie będą przeprowadzane 3 połączenia.&lt;/p&gt;&lt;br /&gt;
&lt;p&gt;Podczas testowania &lt;strong&gt;wydajności skryptów&lt;/strong&gt;, warto pamiętać o włączeniu opcji stałego połączenia HTTP (parametr -k). Dzięki temu, czasy nawiązywania połączeń z serwerem nie wpłyną znacząco na wyniki. Jeżeli natomiast testujemy ogólną &lt;strong&gt;wydajność serwera&lt;/strong&gt; oraz jego dostępność z innych sieci, warto pozostawić domyślne ustawienie.&lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Przykładowe wyniki działania &lt;strong&gt;Apache Benchmark&lt;/strong&gt;:&lt;/h2&gt;&lt;br /&gt;
&lt;h3&gt;Wynik 1&lt;/h3&gt;&lt;pre style='border: 1px solid #e6e6e6; padding: 2px;'&gt;This is ApacheBench, Version 2.0.40-dev &amp;lt; 
                            $Revision: 1.121.2.8 $&amp;gt; apache-2.0 
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, 
                            http://www.zeustech.net/ 
Copyright (c) 1998-2002 The Apache Software Foundation, 
                            http://www.apache.org/  

Benchmarking xxxxxxxxxxxxxxx (be patient).....done 


Server Software:        Apache/2.0.49 
Server Hostname:        xxxxxxxxxxxxxxx 
Server Port:            80 

Document Path:          /xxxxxxxxxxxxxxx/normal.php 
Document Length:        7496 bytes 

Concurrency Level:      3 
Time taken for tests:   6.879892 seconds 
Complete requests:      100 
Failed requests:        0 
Write errors:           0 
Total transferred:      766700 bytes 
HTML transferred:       749600 bytes 
Requests per second:    14.54 [#/sec] (mean) 
Time per request:       206.397 [ms] (mean) 
Time per request:       68.799 [ms] 
                        (mean, across all concurrent requests) 
Transfer rate:          108.72 [Kbytes/sec] received 

Connection Times (ms)                
              min  mean[+/-sd] median   max 
Connect:        0    2   5.2      0      30 
Processing:   130  196  38.3    190     310 
Waiting:       90  172  41.0    160     300 
Total:        130  198  38.5    190     310 

Percentage of the requests served within a certain time (ms) 
      50%    190 
      66%    210 
      75%    220 
      80%    230 
      90%    260 
      95%    270 
      98%    300 
      99%    310 
     100%    310 (longest request)
&lt;/pre&gt;&lt;br /&gt;
&lt;h3&gt;Wynik 2&lt;/h3&gt;&lt;pre style='border: 1px solid #e6e6e6; padding: 2px;'&gt;This is ApacheBench, Version 2.0.40-dev &amp;lt; 
                             $Revision: 1.121.2.8 $&amp;gt; apache-2.0    
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, 
                             http://www.zeustech.net/    
Copyright (c) 1998-2002 The Apache Software Foundation, 
                             http://www.apache.org/        

Benchmarking xxxxxxxxxxxxxxx (be patient).....done        

Server Software:        Apache/2.0.49    
Server Hostname:        xxxxxxxxxxxxxxx    
Server Port:            80      

Document Path:          /xxxxxxxxxxxxxxx/normal.php    
Document Length:        7496 bytes      

Concurrency Level:      3    
Time taken for tests:   5.988612 seconds    
Complete requests:      100    
Failed requests:        0    
Write errors:           0    
Total transferred:      766700 bytes    
HTML transferred:       749600 bytes    
Requests per second:    16.70 [#/sec] (mean)    
Time per request:       179.658 [ms] (mean)    
Time per request:       59.886 [ms] 
                        (mean, across all concurrent requests)    
Transfer rate:          124.90 [Kbytes/sec] received        

Connection Times (ms)
                min  mean[+/-sd] median   max    
Connect:        0    2   4.7      0      20    
Processing:    90  168  48.7    160     350    
Waiting:       70  144  50.3    140     330    
Total:         90  170  48.5    160     350        

Percentage of the requests served within a certain time (ms)
      50%    160
      66%    180
      75%    190
      80%    210
      90%    230
      95%    260
      98%    330
      99%    350
     100%    350 (longest request)
&lt;/pre&gt;&lt;br /&gt;
&lt;p&gt;W obu przypadkach testowany był jeden &lt;strong&gt;skrypt PHP&lt;/strong&gt;. Podczas pierwszego testu, skrypt łączył się z &lt;strong&gt;serwerem MySQL&lt;/strong&gt; za pomocą funkcji &lt;strong&gt;mysql_connect&lt;/strong&gt;(). Podczas drugiego testu, skrypt został zmodyfikowany tak, aby do połączenia z serwerem MySQL używał funkcji &lt;strong&gt;mysql_pconnect&lt;/strong&gt;() która otwiera &lt;strong&gt;stałe połączenie&lt;/strong&gt; z serwerem MySQL. Jak widać, użycie stałego połączenia z bazą przyspieszyło stukrotne wykonanie skryptu o 0,89 sekundy. &lt;br /&gt;
&lt;/p&gt;&lt;br /&gt;
&lt;h2&gt;Polecam uwadze&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://httpd.apache.org/docs/2.0/programs/ab.html"&gt;strona programu &lt;strong&gt;Apache Benchmark&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;manual PHP: &lt;a href="http://pl.php.net/mysql_connect"&gt;mysql_connect&lt;/a&gt;, &lt;a href="http://pl.php.net/mysql_pconnect"&gt;mysql_pconnect&lt;/a&gt;, &lt;a href="http://pl.php.net/manual/pl/features.persistent-connections.php"&gt;informacje o stałych połączeniach&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-4982051803295691326?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/MCKpvLcx4Cc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/4982051803295691326/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/02/kontrola-wydajnosci-apache-benchmark.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4982051803295691326?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4982051803295691326?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/MCKpvLcx4Cc/kontrola-wydajnosci-apache-benchmark.html" title="Kontrola wydajności - Apache Benchmark" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/02/kontrola-wydajnosci-apache-benchmark.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUANQ38zeSp7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-8955362367792173554</id><published>2006-01-19T23:15:00.000+01:00</published><updated>2010-06-13T23:16:32.181+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-13T23:16:32.181+02:00</app:edited><title>Wykrywanie języka użytkownika</title><content type="html">Często pojawia się pytanie, jak - w wielojęzycznych serwisach - ustalić język, w którym powinna pojawić się zawartość strony. Przeszukując Internet można znaleźć kilka rozwiązań:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;ustalać język na podstawie adresu IP&lt;/li&gt;
 &lt;li&gt;ustalać język na podstawie nazwy domenowej hosta&lt;/li&gt;
 &lt;li&gt;ustalić język użytkownika na podstawie informacji o wersji językowej przeglądarki&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Niestety, każda z powyższych metod ma swoje wady: użytkownik może łączyć się przez serwer proxy, więc adres IP nie jest miarodajny, może używać przeglądarki w innej wersji językowej, niż jego język itd. Te wady można by wymieniać, ale nie o to tutaj chodzi.&lt;br /&gt;
&lt;br /&gt;
Najlepszą moim zdaniem metodą wykrycia języka użytkownika, jest ustalenie go &lt;strong&gt;na podstawie nagłówków http&lt;/strong&gt; wysyłanych przez przeglądarkę. Każda nowoczesna przeglądarka pozwala użytkownikowi wybrać w ustawieniach programu preferowane języki. Na wybranie języków pozwala użytkownikowi nawet Internet Explorer. Wybór ten jest następnie wysyłany do serwera w postaci nagłówka &lt;strong&gt;Accept-Language&lt;/strong&gt;.&lt;br /&gt;
&lt;br /&gt;
Zawartość tego nagłówka ma postać listy dwuliterowych kodów języków rozdzielonych znakiem przecinka, np:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;pl,en&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Kod języka może być także uzupełniony o oznaczenie dialektu:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;pl,en-gb&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Ten zapis oznacza, że użytkownik zna język polski i angielski (brytyjski), ale preferuje ten pierwszy.&lt;br /&gt;
&lt;br /&gt;
W przypadku podania kilku języków, każdy z nich może być oznaczony dodatkowym parametrem q (quality). Parametr ten oznacza wagę danego języka. q może przyjmować wartości od 0 do 1. 0 oznacza język ignorowany. Waga języka jest zapisywana jako q=xx i oddzielona od kodu języka średnikiem (;):&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;span style='background-color: #f2f2f2;'&gt;pl&lt;/span&gt;,&lt;span style='background-color: #f2f2f2;'&gt;en;q=0.9&lt;/span&gt;,&lt;span style='background-color: #f2f2f2;'&gt;ru;q=0.8&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Jeżeli dany język nie posiada wagi, przypisuje mu się domyślnie wagę 1. W powyższym przykładzie wagi języków wynoszą:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;polski - 1&lt;/li&gt;
 &lt;li&gt;angielski - 0.9&lt;/li&gt;
 &lt;li&gt;rosyjski - 0.8&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
W PHP zawartość nagłówka Accept-Language jest dostępna w zmiennej &lt;code&gt;$_SERVER['HTTP_ACCEPT_LANGUAGE']&lt;/code&gt;.Przy użyciu niezbyt skomplikowanej funkcji można określić język, w którym należy wyświetlić zawartość strony:&lt;br /&gt;
&lt;br /&gt;
&lt;code style='margin: 0px; padding: 0px; white-space: pre; font-size: 0.9em;'&gt;&lt;br /&gt;
function getLanguage($sDefault, $ihSystemLang){&lt;br /&gt;
    $sLangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];&lt;br /&gt;
    preg_match_all(&lt;br /&gt;
        '!([a-zA-Z]+)(?:-[a-zA-Z]+)?(?: *; *q *= *([01]\.[0-9]+))?!', &lt;br /&gt;
        $sLangs, $shFound);&lt;br /&gt;
    foreach($shFound[1] as $i =&gt; $sLang){&lt;br /&gt;
        $iW = (float)$shFound[2][$i];&lt;br /&gt;
        $ihUserLang[$sLang] = $iW &gt; 0 ? $iW : 1;&lt;br /&gt;
    }&lt;br /&gt;
    $iChoiceWeight = 0;&lt;br /&gt;
    $sChoiceLang = '';&lt;br /&gt;
    foreach($ihSystemLang as $sLang =&gt; $iW){&lt;br /&gt;
        if(isset($ihUserLang[$sLang])){&lt;br /&gt;
            $iTmpChoice = $iW * $ihUserLang[$sLang];&lt;br /&gt;
            if($iTmpChoice &gt; $iChoiceWeight and $iTmpChoice &gt; 0){&lt;br /&gt;
                $iChoiceWeight = $iTmpChoice;&lt;br /&gt;
                $sChoiceLang = $sLang;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    return $sChoiceLang != '' ? $sChoiceLang : $sDefault;&lt;br /&gt;
}&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Funkcja przyjmuje dwa parametry: kod domyślnego języka oraz tablicę asocjacyjną dostępnych na stronie języków wraz z ich wagami. Funkcja zwraca kod języka wybranego na podstawie ustawień przeglądarki. W czasie wyznaczania języka, ignorowane jest oznaczenie dialektu (gdyż niepotrzebnie komplikuje wybór).&lt;br /&gt;
&lt;br /&gt;
Jeżeli strona jest dostępna w języku polskim i angielskim oraz posiada &lt;strong&gt;częściowe&lt;/strong&gt; tłumaczenie na język francuski, wywołanie funkcji może przyjąć postać:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$sLang = getLanguage('pl', array('pl' =&gt; 1, 'en' =&gt; 0.9, 'fr' =&gt; 0.4));&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Językiem podstawowym serwisu jest polski, dlatego właśnie ten język jest podany jako domyślny (pierwszy parametr funkcji) oraz na liście wag otrzymuje 1. Język angielski otrzymuje wysoką wagę 0.9, natomiast francuski tylko 0.4 (ze względu na to, że tłumaczenie jest tylko częściowe). &lt;br /&gt;
&lt;br /&gt;
Wynik działania funkcji zależy od nagłówka &lt;strong&gt;Accept-language&lt;/strong&gt;. Przykładowe wyniki:&lt;br /&gt;
&lt;br /&gt;
&lt;dl&gt;&lt;dt&gt;Accept-language: pl,en&lt;/dt&gt;
&lt;dd&gt;wynik: pl&lt;/dd&gt;

&lt;dt&gt;Accept-language: fr,en&lt;/dt&gt;
&lt;dd&gt;wynik: en&lt;/dd&gt;

&lt;dt&gt;Accept-language: fr,en;q=0.3&lt;/dt&gt;
&lt;dd&gt;wynik: fr&lt;/dd&gt;
&lt;/dl&gt;&lt;br /&gt;
W przypadku, gdy w nagłówku nie ma żadnego z języków obsługiwanych przez stronę, funkcja zwróci kod domyślny (podany jako pierwszy parametr).&lt;br /&gt;
&lt;br /&gt;
Na koniec kilka słów wyjaśnienia, dlaczego uważam metodę wyboru języka na podstawie nagłówka Accept-language za najlepszą. Oczywiście, jest ona tak samo zawodna jak te, które wymieniłem wcześniej. W końcu, użytkownik może korzystać z przeglądarki w której domyślnie jest ustawiony język inny niż język, którym posługuje się użytkownik. Może używać komputera w pracy za granicą itd.&lt;br /&gt;
Jednak &lt;strong&gt;powyższa metoda jest jedyną&lt;/strong&gt; z wymienionych, w której do wykrycia języka używamy parametru, nad którym &lt;strong&gt;użytkownik ma całkowitą kontrolę&lt;/strong&gt;. Przecież nie zawsze możemy wpływać na adres IP z którego łączymy się z siecią (przebywamy za granicą, używamy serwera proxy itp). Możemy używać ulubionej przeglądarki w wersji językowej innej niż nasz język gdyż nie jest jeszcze dostępne tłumaczenie. Nie zmienimy wersji językowej, ale preferowane języki możemy zmienić!&lt;br /&gt;
&lt;br /&gt;
Oczywiście trudno wymagać od użytkowników aby wybierali język strony poprzez ustawienia przeglądarki - dlatego więc nie należy zapominać o udostępnieniu użytkownikowi możliwości zmiany języka na wypadek, gdyby funkcja dokonała złego wyboru.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-8955362367792173554?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/97MA8mpXn5E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/8955362367792173554/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2006/01/wykrywanie-jezyka-uzytkownika.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8955362367792173554?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/8955362367792173554?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/97MA8mpXn5E/wykrywanie-jezyka-uzytkownika.html" title="Wykrywanie języka użytkownika" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2006/01/wykrywanie-jezyka-uzytkownika.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4NRns9eip7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-5892110012341584625</id><published>2006-01-15T23:13:00.000+01:00</published><updated>2010-06-27T00:23:17.562+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:23:17.562+02:00</app:edited><title>Optymalizacja bazy MySql - część II</title><content type="html">Wcześniej (&lt;a href="http://www.netcoffee.pl/pogodzinach/2005/10/30/predkosc-czy-miejsce/"&gt;Prędkość czy miejsce&lt;/a&gt;) pisałem, jak można zaprojektować tabelę MySQL aby baza działała szybciej lub zajmowała mniej miejsca. Teraz przedstawiam garść innych porad:&lt;br /&gt;
&lt;br /&gt;
&lt;dl&gt;&lt;dt&gt;używaj pól o jak najmiejszym rozmiarze&lt;/dt&gt;
&lt;dd&gt;   zmniejszenie rekordu redukuje ilość operacji dyskowych. Jeżeli przechowujesz hasło    w postaci hasza md5, użyj pola char(32) a nie varchar(100)  &lt;/dd&gt;
&lt;dt&gt;deklaruj kolumny jako NOT NULL&lt;/dt&gt;
&lt;dd&gt;   zmniejszysz rozmiar rekordu   &lt;/dd&gt;
&lt;dt&gt;dla tabel MyISAM używaj rekordu o stałej długości&lt;/dt&gt;
&lt;dd&gt;   patrz: &lt;a href="http://www.netcoffee.pl/pogodzinach/2005/10/30/predkosc-czy-miejsce/"&gt;Prędkość czy miejsce&lt;/a&gt;  &lt;/dd&gt;
&lt;dt&gt;podstawowy indeks tabeli powinien być tak mały, jak to możliwe&lt;/dt&gt;
&lt;dd&gt;   przyspieszy to przeszukiwanie i tworzenie pliku indeksu  &lt;/dd&gt;
&lt;dt&gt;twórz indeksy których naprawdę potrzebujesz&lt;/dt&gt;
&lt;dd&gt;   tworzenie i odczytywanie indeksu też zajmuje czas. Indeksuj te kolumny, których używasz   w części WHERE, ORDER BY lub GROUP BY zapytania. Nie musisz indeksować kolumn, których używasz tylko w części   SELECT  &lt;/dd&gt;
&lt;dt&gt;pierwsza kolumna złożonego klucza powinna być kolumną najczęściej używaną&lt;/dt&gt;
&lt;dd&gt;   przyspieszy to przeszukiwanie indeksu  &lt;/dd&gt;
&lt;dt&gt;jeżeli pobierasz wiele kolumn z tabeli, najpierw użyj kolumny posiadającej wiele duplikatów&lt;/dt&gt;
&lt;dd&gt;&lt;/dd&gt;
&lt;dt&gt;jeżeli kolumna ma unikalny prefiks na kilku pierwszych znakach, lepiej jest zindeksować tylko te kilka pierwszych znaków&lt;/dt&gt;
&lt;dd&gt;   użyj do tego funkcji MySQL pozwalającej na tworzeniu indeksu lewej części kolumny znakowej   &lt;/dd&gt;
&lt;dt&gt;w pewnych warunkach może przydać się podzielenie tabeli na dwie&lt;/dt&gt;
&lt;dd&gt;   w ten sposób możesz uzyskać w jednej tabeli rekordy o stałej długości   &lt;/dd&gt;
&lt;dt&gt;indeksy działają wydajniej dla kolumn, które mają dużą ilość unikalnych wartości w stosunku do ilości rekordów (ang. &lt;span lang='en'&gt;cardinality&lt;/span&gt;)&lt;/dt&gt;
&lt;dd&gt;   lepiej działa indeks dla kolumny 'login' (wszystkie wartości unikalne) niż dla kolumny 'wojewodztwo' (tylko 16 różnych wartości)  &lt;/dd&gt;
&lt;dt&gt;zwróć uwagę na typ pól podczas porównywania&lt;/dt&gt;
&lt;dd&gt;   jeżeli wyszukujesz rekordy na podstawie pola typu INTEGER, nie używaj porównania tego pola z typem STRING   (np. WHERE id = '78'). Lepiej porównuj pola tego samego typu (np. WHERE id = 78). Jeżeli porównujesz wartość   pola z wartością innego pola, zadbaj aby miały one ten sam typ. Dzięki temu unikniesz konieczności rzutowania.   Pamiętaj także, że int i bigint to inne typy pól oraz char(10) nie odpowiada polu char(12).  &lt;/dd&gt;
&lt;dt&gt;staraj się umieszczać indeksowane samotnie po jednej stronie porównania&lt;/dt&gt;
&lt;dd&gt;   w przeciwnym wypadku MySQL nie będzie mógł użyć indeksów.   Lepszą wydajność uzyskasz pisząc 'WHERE i &lt; 4 / 2' niż 'WHERE i *2 &lt; 2' 
 &lt;/dd&gt;
&lt;dt&gt;nie używaj znaków wieloznacznych na początku porówanania LIKE&lt;/dt&gt;
&lt;/dd&gt;&lt;dd&gt;&lt;/dd&gt;
&lt;dt&gt;używaj pól typu ENUM jeżeli to możliwe&lt;/dt&gt;
&lt;dd&gt;&lt;/dd&gt; &lt;/dl&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-5892110012341584625?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/n3QUhxBjcwA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/5892110012341584625/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2010/06/optymalizacja-bazy-mysql-czeae-ii.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/5892110012341584625?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/5892110012341584625?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/n3QUhxBjcwA/optymalizacja-bazy-mysql-czeae-ii.html" title="Optymalizacja bazy MySql - część II" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2010/06/optymalizacja-bazy-mysql-czeae-ii.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEUCSXk9eCp7ImA9WxFVFEs.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-523901115864427097</id><published>2005-10-30T23:56:00.000+01:00</published><updated>2010-06-13T23:57:48.760+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-13T23:57:48.760+02:00</app:edited><title>Predkosc czy miejsce</title><content type="html">Podczas pracy z MySQL, warto wiedziedziec o mozliwosci wplyniecia na wydajnosc bazy, juz na etapie projektowania jej struktury. W zaleznosci od zasobow ktorymi dysponujemy, mozemy zaprojektowac baze tak, aby dane w niej przechowywane zajmowaly jak najmniej miejsca, lub tak, aby wzrosla wydajnosc.&lt;br /&gt;
&lt;br /&gt;
Model I - oszczedzamy miejsce&lt;br /&gt;
Podczas projektowania struktury bazy danych, wybieramy taki typ pol, aby zminimalizowac przestrzen dyskowa potrzebna do przechowywania danych. Jednym z typow pol pozwalajacych na oszczednosc miejsca jest VARCHAR. Rozmiar danych zapisanych w tym polu to L+1 bajtow, gdzie L to dlugosc danych zapisanych w kolumnie. Tak wiec 'string' zajmuje 6+1 = 7 bajtow (6 na zapisanie danych + 1 na oznaczenie konca danych).&lt;br /&gt;
&lt;br /&gt;
Model II - zwiekszamy wydajnosc&lt;br /&gt;
Jezeli zastapimy pole typu &lt;em&gt;VARCHAR&lt;/em&gt; (np. &lt;em&gt;VARCHAR(30)&lt;/em&gt;) polem typu &lt;em&gt;CHAR&lt;/em&gt; (tu: &lt;em&gt;CHAR(30)&lt;/em&gt;), zwiekszy sie ilosc przestrzeni dyskowej potrzebnej na zapisanie danych. Pole typu CHAR zawsze zajmuje maksymalna ilosc przestrzeni dyskowej - tu 30 bajtow. Jezeli dane wstawione do takiego pola sa mniejsze niz 30 bajtow (np. 6), sa uzupelniane spacjami. To wyraznie zwieksza objetosc danych.&lt;br /&gt;
&lt;br /&gt;
Jednak wykorzystanie modelu II niesie za soba znaczne zwiekszenie szybkosci wyszukiwania danych. Kazdy rekord zajmuje zawsze tyle samo miejsca, latwiej jest wiec wyszukac szukany rekord (aby odnalesc poczatek rekordu w pliku danych, wystarczy przemnozyc nr rekordu przez jego dlugosc). W przypadku modelu I nie jest to juz takie proste, gdyz wczesniejsze rekordy zajmuja rozna ilosc miejsca.&lt;br /&gt;
&lt;br /&gt;
Podczas pracy z MySQL nalezy pamietac, ze rekord powinien zawierac wyszystkie pola typu CHAR (ogolnie: pola o stalej dlugosci) lub VARCHAR (oglonie: o zmiennej dlugosci). Jezeli tabela zawiera juz pole typu VARCHAR, to podczas dodawania pola CHAR zostanie ono dodane jako VARCHAR. Dlaczego? Otoz wstawienie pola CHAR nie zmieni formatu rekordu na staly gdyz istnieja w nim inne pola o zmiennej dlugosci. Nie niesie wiec za soba korzysci. Natomiast wstawienie pola VARCHAR zmniejszy ilosc potrzebnego miejsca. Dlatego podczas projektowania tabeli, nalezy wybrac odpowiadajacy nam model i konsekwentnie wybierac wymagane typy pol.&lt;br /&gt;
&lt;br /&gt;
Niejednokrotnie jestesmy zmuszeni do umieszczenia w tabeli pola ktorego typ nie ma stalej dlugosci (np. TEXT) i nie mozemy uzyskac rekordu o stalej dlugosci. W takim przypadku warto rozwazyc przeniesienie pol o zmiennej dlugosci do innej tabeli (np. w przypadku forum, w jednej tabeli mozna przechowywac "metadane" wpisu - tytul, daty, id autora itd, a w drugiej sama tresc). Jednak takie rozwiazanie nalezy dobrze przemyslec w kontekscie projektu gdyz moze w rezultacie pogorszyc wydajnosc.&lt;br /&gt;
&lt;br /&gt;
Inna wada Modelu I jest to co znamy z systemu plikow FAT - fragmentacja danych zapisywanych w pliku podczas edycji lub dodawania rekordow.&lt;br /&gt;
Uzycie Modelu II nie tylko nie powoduje fragmentacji danych, ale  niesie za soba takze inne korzysci - prawdopodobienstwo naprawienia uszkodzonej tabeli jest o wiele wieksze, jezeli rekordy maja stala dlugosc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-523901115864427097?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/dBkOL84xEWQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/523901115864427097/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2005/10/predkosc-czy-miejsce.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/523901115864427097?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/523901115864427097?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/dBkOL84xEWQ/predkosc-czy-miejsce.html" title="Predkosc czy miejsce" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2005/10/predkosc-czy-miejsce.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04ARX8-fSp7ImA9WxFUFUU.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-4938323926772882996</id><published>2005-10-30T00:04:00.001+02:00</published><updated>2010-06-27T00:05:44.155+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:05:44.155+02:00</app:edited><title>Nie duplikuj informacji w bazie</title><content type="html">Jedna z istotnych cech relacyjnych baz danych jest to, ze mozna przechowywane w nich informacje powiazac ze soba. Nie ma wiec koniecznosci duplikowania informacji zawartych w bazie.&lt;br /&gt;
&lt;br /&gt;
Dla przykladu wezmy pod uwage system zawierajacy konta uzytkownikow, ktore mozna dowolnie przypisywac do grup. Istnieje takze mozliwosc wyslania wiadomosci e-mail do wybranej grupy.&lt;br /&gt;
&lt;br /&gt;
Uproszczona struktura bazy danych to:&lt;br /&gt;
Tabela 'uzytkownik':&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;id&lt;/li&gt;
 &lt;li&gt;login&lt;/li&gt;
 &lt;li&gt;email&lt;/li&gt;
&lt;/ul&gt;Tabela 'grupa':&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;id&lt;/li&gt;
 &lt;li&gt;nazwa&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
W bazie danych znajduje sie jeszcze jedna tabela - zawiera ona informacje o przynaleznosci poszczegolnych uzytkownikow do grup. &lt;br /&gt;
Blednie zaprojektowana tabela bedzie wygladac nastepujaco:&lt;br /&gt;
Tabela 'uzytkownik_grupa':&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;grupa_id&lt;/li&gt;
 &lt;li&gt;uzytkownik_id&lt;/li&gt;
 &lt;li&gt;email&lt;/li&gt;
&lt;/ul&gt;W zalozeniu, aby pobrac adresy e-mail uzytkownikow nalezacych do grupy o id = 7, wystarczy wywolac zapytanie:&lt;br /&gt;
&lt;code&gt;SELECT email FROM uzytkownik_grupa WHERE grupa_id = 7&lt;/code&gt;&lt;br /&gt;
Niestety, takie rozwiazanie ma powazne wady:&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;informacja o adresie e-mail uzytkownika jest zduplikowana - podczas jej uaktualniania, nalezy ja zmienic w dwoch tabelach&lt;/li&gt;
 &lt;li&gt;dane niepotrzebnie znajduja sie w drugiej tabeli - przez to zwieksza sie objetosc bazy danych&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Rozwiazaniem probemu, jest zmiana struktury tabeli 'uzytkownik_grupa':&lt;br /&gt;
&lt;ul&gt; &lt;li&gt;grupa_id&lt;/li&gt;
 &lt;li&gt;uzytkownik_id&lt;/li&gt;
&lt;/ul&gt;oraz zmiana sposobu pobierania adresow e-mail:&lt;br /&gt;
&lt;code&gt;SELECT u.email FROM uzytkownik as u, uzytkownik_grupa as ug WHERE u.id = ug.uzytkownik_id AND ug.grupa_id = 7&lt;/code&gt; &lt;br /&gt;
&lt;br /&gt;
Takie rozwiazanie pozwala na redukcje ilosci zapytan do bazy danych w momencie aktualizacji adresu. Zapewnia takze, ze dane pobierane z bazy beda zawsze aktualne. Zmniejsza sie takze objetosc plikow z danymi w bazie - dzieki temu moze wzrosnac jej wydajnosc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-4938323926772882996?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/gf4UCrMcyEg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/4938323926772882996/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2005/10/nie-duplikuj-informacji-w-bazie.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4938323926772882996?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/4938323926772882996?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/gf4UCrMcyEg/nie-duplikuj-informacji-w-bazie.html" title="Nie duplikuj informacji w bazie" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2005/10/nie-duplikuj-informacji-w-bazie.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4GRnw-fyp7ImA9WxFUFUQ.&quot;"><id>tag:blogger.com,1999:blog-2992554473931201923.post-1338033301813972687</id><published>2005-06-04T00:21:00.000+02:00</published><updated>2010-06-27T00:22:07.257+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-27T00:22:07.257+02:00</app:edited><title>Wyrażenia regularne - Jeffrey Friedl</title><content type="html">&lt;img style='float: right; margin-left: 3px;' src="http://helion.pl/okladki/181x236/wyrare.jpg" alt="Wyrażenia Regularne - okładka" /&gt;Wyrażenia regularne to bardzo potężne narzędzie - przydatne nie tylko programiście PHP ale także wielu innych języków. Obsługę WR znajdziemy w wielu językach (&lt;a href="http://www.php.net"&gt;PHP&lt;/a&gt;, Perl, Java, Delphi, JavaScript), edytorach (&lt;a href="http://vim.sourceforge.net"&gt;Vim&lt;/a&gt;, Emacs, &lt;a href="http://www.chami.com/"&gt;Html-Kit&lt;/a&gt;, Nisus Writer) i innych narzędziach (np. &lt;a href="http://www.ghisler.com"&gt;Total Commander&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
WR pozwalają w prosty sposób sprawdzić format i poprawność wprowadzonych danych tekstowych, ale także wyszukiwać fragmenty tekstu. Na przykład, sprawdzenie poprawności podanego adresu bez wykorzystania WR wymagałoby conajmniej jednej pętli i instrukcji warunkowej. Dzięki WR, w PHP sprawa wygląda następująco:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
if(preg_match('/^([a-z0-9_-]+)@([a-z]0-9\.)+([a-z-]{2,5})$/', $sEmail)){&lt;br /&gt;
echo 'Email OK';&lt;br /&gt;
}else{&lt;br /&gt;
echo 'Email błędny';&lt;br /&gt;
}&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
Całość testu sprowadza się do użycia funkcji preg_match() sprawdzającej zgodność zawartości zmiennej $sEmail z wyrażeniem &lt;code&gt;^([a-z0-9_-]+)@([a-z]0-9\.)+([a-z]{2,5})$&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Dodać także należy, że to sprawdzenie nie jest zbyt szczegółowe - sprowadza się jedynie do sprawdzenia, czy podany ciąg znaków zawiera znak @ (at), a przed nim znajduje się przynajmniej jedna litera (lub cyfra lub jeden ze znaków: - _) a za znakiem @ czy znajdują się grupy liter lub cyfr rozdzielone kropkami, przy czym ostatnia grupa może zawierać tylko 2 do 5 liter. &lt;br /&gt;
&lt;br /&gt;
Nie będę teraz wyjaśniał znaczenia poszczególnych części tego wyrażenia. Dlaczego? Zagadnienie WR to temat nad wyraz obszerny - Jeffrey Friedl pisząc &lt;a href="http://helion.pl/ksiazki/wyrare.htm"&gt;Wyrażenia Regularne (ang. Mastering Regular Expressions)&lt;/a&gt; potrzebował 320 stron aby opisać WR! I trzeba przyznać, że zrobił to wybitnie! Pozycja wydana nakładem wydawnictwa &lt;a href="http://www.helion.pl"&gt;Helion&lt;/a&gt;, dostarcza gruntownej i szczegółowej wiedzy na temat wyrażeń regularnych. Co najważniejsze Friedl opisuje nie tylko składnię WR, ale także sam mechanizm, co znacznie ułatwia poznanie tego - jakże wygodnego - narzędzia. &lt;br /&gt;
&lt;br /&gt;
Kolejnym bardzo istotnym atutem tej pozycji jest wybitny przekład oryginału, którego dokonał Adam Podstawczyński.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2992554473931201923-1338033301813972687?l=pogodzinach.netcoffee.pl' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/netcoffee_pl_po_godzinach/~4/mVYq5bMk4Qo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://pogodzinach.netcoffee.pl/feeds/1338033301813972687/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://pogodzinach.netcoffee.pl/2010/06/wyrazenia-regularne-jeffrey-friedl.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1338033301813972687?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2992554473931201923/posts/default/1338033301813972687?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/netcoffee_pl_po_godzinach/~3/mVYq5bMk4Qo/wyrazenia-regularne-jeffrey-friedl.html" title="Wyrażenia regularne - Jeffrey Friedl" /><author><name>~Jaro</name><uri>http://www.blogger.com/profile/03581672540941032211</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/_2_qBG5F8wGc/Sx1-MQhU1dI/AAAAAAAAEeo/9m3yf_d5n-4/S220/mas2.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://pogodzinach.netcoffee.pl/2010/06/wyrazenia-regularne-jeffrey-friedl.html</feedburner:origLink></entry></feed>

