Jako, że zbliża się koniec ważności mojego konta na DreamHost, a przedłużać go zamiaru nawet namniejszego nie mam, zaczęłem się przygotowywać do migracji. Jeszcze nie wiem gdzie pójdę sobie, ale pójdę na pewno :)

Jedną z rzeczy z tym związanych jest usunięcie mojego repo (repo.urzenia.net). Cała jego zawartość znajdzie się niedługo jako konkretne projekty na code.google.com. W sumie już wszystko przeniosłem, poza BlipApi.php, które też się tam niedługo znajdzie. Nowe adresy projektów to:

mPack
zestaw bibliotek pomocnych przy tworzeniu serwisów w PHP: m-pack.googlecode.com.
useless-scripts
kilka skryptów które wykorzystuje w mojej codziennej pracy na komputerze: useless-scripts.googlecode.com. Tutaj też dostał się mój ostatni “produkt” killer.pl.
WP Blip!
Plugin do WordPressa, wyświetlający na nim ostatnie wpisy z serwisu Blip!: wp-blip.googlecode.com.

Jestem na fali: w ciągu całej przerwy świątecznej (dla mnie to okres od 22.12.2007 do 2.01.2008 włącznie) wklepałem całe mnóstwo (kilka tysięcy) linii kodu. Trochę firmowych, trochę prywatnych (nie publikowanych), a trochę (jak te związane z Blip!em) publicznych. Właśnie skończyłem upublicznianie pluginu do WordPressa do wyświetlania ostatnich statusów z Blip!a.

Jakaś dokumentacja pojawi się niedługo na stronie projektu (repo.urzenia.net/PHP:WP_Blip!wp-blip.googlecode.com), w tej chwili sam plugin można pobrać ze strony na Google Code (code.google.com/p/wp-blip).

Krótka instrukcja obsługi:

  • pobrać plik z Google Code, rozpakować go w katalogu
    WORDPRESS_ROOT/wp-content/plugins (zostanie utworzony katalog
    wp-blip)
  • w panelu administracyjnym Wordpressa uaktywnić plugin
  • w Options->WP Blip! ustawić login i hasło do swojego konta w Blip!ie (także kilka innych opcji)
  • gdzieś w szablonie dodać wywołanie funkcji:
    <?php
    if (function_exists ('wp_blip')) { wp_blip("\n", 1); }
    ?>
  • Wszelkie uwagi mile widziane :) Można je zostawić w komentarzach do tego wpisu, lub dowolną inną metodą (kilka metod kontaktu ze mną opisanych jest na podstronie urzenia.net/kontakt).


Twórcy Blip!a wydali wersję 0.02 API, więc zrobiłem co w mojej mocy aby skończyć moją PeHaPową bilbiotekę do tegoż ;) Zmian dużo, do wersji API 0.01 była ona mocno testowa, i w sumie nie jest poprawnie zrobiona, są w niej błędy etc, których nie zamierzam już poprawiać. Wersja 0.02.4 niesie z sobą pełną obsługę protokołu, wykorzystując wszystko co producenci dali, szczegóły w oficjalnej dokumentacji Blip!a.

Szczegóły numeracji biblioteki: pierwszą część stanowi wersja API (w tym wyapdku 0.02), drugą - odsłona samej biblioteki (w tym wypadku 4).

BlipApi.php można używać na dwa sposoby:

  1. wywołując metodę BlipApi::execute(), gdzie pierwszym parametrem jest nazwa komendy do wykonania (spis komend w oficjalnej dokumentacji, jedyną różnicą jest dirmsg zamiast directed_messages), a następnie dostępne parametry metody (szczegóły w pliku klasy i w oficjalnej dokumentacji Blip!a), np.:

    $bapi = new BlipApi ('login', 'haslo');
    $bapi->connect ();
    $bapi->execute ('update_read', null, 'mysz');

    Pobierze ostatnie 10 statusów użytkownika mysz.

  2. wywołując komendę jako konkretną metodą obiektu BlipApi:

    $bapi->update_read (null, 'mysz');

    Powyższe dwa wywołania są sobie równoważne.

Jeśli ktoś znajdzie jakieś błędy, lub ma uwagi co do samej biblioteki, proszę o komentarze tutaj, lub kontakt mailowy: urzenia.net/email. Z góry dziękuję za feedback ;)

Bibliotekę można pobrać/obejrzeć z: repo.urzenia.net/files/blipapi-0.02.phps. W tej chwili nie ma skąd pobrać samej biblioteki, jako że usunąłem repo.urzenia.net. Dołączona jest za to do pakietu WP Blip!, skąd można ją “ręcznie” wyciągnąć :) Przepraszam za kłopot :)


Od kilku tygodni bawię się po trochu CURLem, w ramach tworzenia PeHaPowej biblioteki do Blip!a. Poniżej kilka zagwozdek, na które ciężko znaleźć sensowną odpowiedź w google, albo ja nie wiem jak pytać…

  1. Jak poprawnie wykonać zapytanie PUT? (aka: metoda PUT i “select/poll returned error”)

    Nad tym spędziłem najwięcej czasu. Kombinowałem na mnóstwo sposobów. Kluczem do sukcesu okazało się podejrzenie wersji konsolowej CURLa z dokładnie takim samym zapytaniem, jakie wysyłałem za pomocą skryptu PHP. W skrócie: należy wymusić na CURLu HTTP 1.0, za pomocą ustawienia opcji:

    curl_setopt ($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); # gdzie $ch jest obiektem utworzonym za pomocą curl_init ()

    Po tej operacji zapytania śmigają jak należy (dodam tylko, że czasem zamiast “select/poll returned error” dostawałem “connection reset by peer”).

  2. Przy rozwiązywaniu powyższego problemu, pomogła mi mała sztuczka. Mianowicie musiałem podejrzeć jakie nagłówki wysyła CURL. Normalnie PHPowy CURL nie zamierza pokazać co jest wysyłane i odbierane (Rozpisałem się - oczywiście nagłówki odbierane bez problemu można zobaczyć. Dzięki, Arek ;) ), bo i po co… Ja posłużyłem się tutaj specjalnie na tą okazję utworzonym skryptem PHP z dokładnie wyizolowanym ‘testcase’. Tutaj, po zaimplementowaniu opcji:

    curl_setopt ($ch, CURLOPT_VERBOSE, 1);

    i odpaleniu skryptu z konsoli, grzecznie została mi pokazana komunikacja wychodząca z serwerem, co było kluczem do rozwiązania problemu (okazało się że był wysyłany dodatkowy nagłówek Expect należący do HTTP 1.1, a po wrzuceniu tego w google znalazłem informacje które mnie nakierowały na właściwy trop).

  3. Jak wysłać plik POSTem?

    Ech, w sumie rozwiązanie jest banalne, jeśli ktoś dokładniej przeczyta manual na stronach PHP dotyczący CURLa. Ale skoro już opisuję zagwozdki…

    PHPowy CURL pozwala wysyłać dane na dwa sposoby (akceptowane i sprecyzowane, oczywiście, w odpowiednim RFC). Jeden to x-www-form-urlencoded (tak są wysyłane dane za pomocą webowych formularzy), drugi to multi-part form-data (tak są wysyłane pliki, także z formularzy). Normalnie dla opcji CURLOPT_POSTFIELDS podaje się string który wygląda dokładnie tak samo jak ten widoczny w URLach skryptów pehapowych (mówiąc po laicku), np. a=1&b=2 - wtedy dane są wysyłane metodą x-www-form-urlencoded. Aby zmusić CURLa do wysłania danych metodą multi-part form-data, jako parametr opcji CURLOPT_POSTFIELDS musimy podać tablicę. Tak, właśnie tablicę, gdzie kluczem powinne być nazwa pola (odpowiadająca temu z prawdziwego formularza), a wartością ścieżka do pliku, ale uwaga: ścieżka powinna być poprzedzona znakiem “małpy”: @. Czyli powinno wyglądać to tak:

    curl_setopt ($ch, CURLOPT_POSTFIELDS, array ('pole' => '@/sciezka/do/pliku.jpg'));

    Po tej operacji wysłanie pliku nie powinno być już problemem :)


Jakiś czas temu zacząłem się bawić Blip!em. Wstawiłem sobie nawet na główną stronę myGeeBloga wklejkę z ostatnim statusem ;) Ichnich wklejki sa jak dla mnie kiepskie (flasha nie lubię, a wersja jsowa pozwala na pobranie tylko ostatniego statusu), więc pomyślałem o pluginie do WordPressa. Żeby móc to urzeczywistnić, potrzeba mi jakiejś klasy do obsługi tego - a z pomocą udostępnionego API zrobiłem sobie taką. Na razie wersja pre-alpha ;)

do podejrzenia:
repo.urzenia.net/files/blipapi.phps
do pobrania:
http://repo.urzenia.net/files/get.php?f=blipapi.php

O feedback poproszę za pomoca formularza: urzenia.net/email lub na liście blip-api: groups.google.com/group/blip-devel/browse_thread/thread/3096e1724d036f55.

UPDATE: wprowadziłem kilka zmian w klasie blipapi, częściowo kosmetycznych, częściowo optymalizacyjnych. Do pobrania pod wymienionymi wyżej adresami :)


W PHP5 został wprowadzony nowy, zupełnie odmienny, i znacznie pełniejszy model obiektowy (w porównaniu do PHP4). W modelu tym znalazło się miejsce dla kilku magicznych metod, które chciałbym tutaj omówić.

Dwoma z najprzyjemniejszych metod magicznych w PHP5 są __set() i __get(), które pozwalają na przechwytywanie odwołań do nieistniejących (lub niedostępnych z powodu akcesorów) zmiennych obiektu.

Wcześniej, przed wersją piątą PHP, jedynym sposobem na pobranie/ustawienie właściwości w pewien uniwersalny i umożliwiający rozwój sposób było stosowanie znanych m.in. z Javy setterów/getterów, które zwyczajowo mają postać:

class Test1 {
    var $elem1;
    function getElem1();
    function setElem1($value);
}

Używałem tej samej metody, jako że innej nie było. Ale magiczne właściwości __set() i __get() załatwiają dla nas masę pustego klepania kodu. Sposób użycia:

class Test1 {
    private $elem1;
    function __get($var) {
        return $this->$var;
    }
    function __set($var, $value) {
        $this->$var = $value;
    }
}

Oczywiście powyżej można zawrzeć także sprawdzanie czy dana właściwość istnieje w obiekcie, ale to już osobna kwestia. Co nam daje powyższy zapis? To, że dla drugiej właściwości, w metodzie “starej”, trzeba by utworzyć kolejne metody setXXX() i getXXX(), natomiast przy wykorzystaniu metod magicznych pozostaje nam dodać tylko kolejne pole do definicji klasy:

[...]
    private $elem1;
    private $elem2;
[...]

Ktoś może powiedzieć: ale ręczne tworzenie setterów i getterów daje nam możliwość bardziej zaawansowanej walidacji poprawności wprowadzanych danych etc. Owszem, zawsze do bardziej zaawansowanej walidacji potrzebna będzie osobna metoda (no dobrze, można to ominąć kosztem zaciemnienia kodu, ale to nie jest dobre rozwiązanie…), ale to nie powód żeby zrezygnować z magii:

class Test2 {
	private $integer;
	private $string;

	public function __get($var) {
		return $this->$var;
	}
	public function __set($var, $value) {
		if (method_exists($this, '_check__'.$var)) {
			if (call_user_func_array(array($this, '_check__'.$var), $value)) {
				$this->$var = $value;
			} else {
				throw new Exception('zła wartość właściwości '. $var);
			}
		} else {
			$this->$var = $value;
		}
	}
	private function _check__integer($data) {
		return is_int($data);
	}
	private function _check__string($data) {
		return is_string($data);
	}
}

Powyższy kod pozwala na dodawanie dowolnej ilości właściwości klasy, a po dodaniu funkcji sprawdzającej także walidację tejże właściwości. Trzeba tylko pamiętać o właściwej nazwie funkcji sprawdzającej, ale to samo dotyczy tradycyjnych getterów :).

Inną magiczną funkcją w PHP5 jest __call(), która pełni podobną funkcję jak __set() i __get(), tylko że dotyczy metod obiektu: przechwytuje odwołania do nieistniejących metod obiektu (niestety, nie dotyczy to metod o zastrzeżonym dostępie). Przykład zastosowania:

class Test3 {
	public $data;
	public function __call($method, $args) {
		if (preg_match('/^get([A-Z][a-z]+)$/', $method, $matches)) {
			settype($this->data, strtolower($matches[1]));
			return $this->data;
		}
	}
}

Przykład powyżej pozwala na pobranie właściwości o typie takim jaką metodą ją wywołamy. Dla wywołania: $test3->getInteger() otrzymamy wartość o typie integer, dla $test3->getString() - ciąg znaków etc.

Jeśli korzystamy z magicznych __set() i __get() jako funkcji dostępowych do właściwości, trzeba czasem sprawdzać czy dana właściwość jest ustawiona czy też jej nie ma. Nie pomoże tutaj ani property_exists() (oczywiście zakładając tutaj że nasze właściwości obiektu mają ustawiony akcesor private lub protected), ani nawet get_object_vars(). Jedyną możliwością jest dodanie metody __isset(), która będzie zwracała odpowiednio prawdę lub fałsz. Dzięki niej będzie możliwe wykorzystanie wbudowanej instrukcji isset() na właściwości klasy, żeby można było stwierdzić czy dana właściwość istnieje:

class Test4 {
	private $data;
	public function __isset($var) {
		return (property_exists($this, $var));
	}
}

Wystarczy teraz użyć:

isset($test4->data)

żeby można było sprawdzić istnienie właściwości.

Skoro istnieje możliwość sprawdzania istnienia właściwości, powinna być też możliwość jej usunięcia - do tego służy metoda __unset(), której przykład można zobaczyć poniżej:

class Test5 {
	private $_data = array();
	public function __set($var, $value) {
		$this->_data[$var] = $value;
	}
	public function __get($var) {
		return $this->_data[$var];
	}
	public function __isset($var) {
		return array_key_exists($var, $this->_data);
	}
	public function __unset($var) {
		unset($this->_data[$var]);
	}
}

Dzięki zastosowaniu __unset() stało się możliwe usuwanie poszczególnych właściwości obiektu za pomocą wbudowanej funkcji unset():

$o = new Test5();
$o->a = 1;
$o->b = 2;
$o->c = 3;

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

echo '<br />usuwamy a:<br />';
unset($o->a);

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

echo '<br />usuwamy c:<br />';
unset($o->c);

echo 'właściwość "a": '; var_dump (isset($o->a)); echo '<br />';
echo 'właściwość "b": '; var_dump (isset($o->b)); echo '<br />';
echo 'właściwość "c": '; var_dump (isset($o->c)); echo '<br />';

Ostatnią z opisywanych przeze mnie magicznych funkcji jest użyteczne szczególnie przy debugowaniu metoda __toString(). Pozwala ona na zdefiniowanie ciągu znaków jaki zostanie wyświetlony lub zwrócony podczas rzutowania naszego obiektu na typ string, czyli np. gdy zechcemy wyświetlić nasz obiekt. Przykład:

class Test6 {
	public function __toString() {
		return 'To moja klasa ' . get_class($this);
	}
}

Gdy teraz chcemy sprawdzić instancję jakiej klasy mamy w zmiennej, wystarczy wyprintować ją za pomocą print() lub echo():

$test6 = new Test6();
echo $test6;

To nie wszystkie magiczne właściwości jakich dostarcza nam PHP5, ale pozostałych nie będę tu omawiał ze względu na rozwlekłość artykułu, a także na nieco bardziej zaawansowaną tematykę. W powyższych przykładach brakuje oczywiście dokładnego sprawdzania błędów i innych udoskonaleń, ale w końcu nie o tym był ten artykuł :)

PS. Kilka przykładów użycia magicznych właściwości znajduje się też w innym moim wpisie: Obiekt jako tablica (wprowadzenie do SPL).


Niedawno spotkałem się z pewną… ciekawostką, którą podsunął mi jeden ze współuczestników szkolenia w jakim brałem udział. W języku PHP można komentować kod na kilka sposobów:

  1. <?php
    # to jest komentarz jednolinijkowy
    ?>
  2. <?php
    // to jest komentarz jednolinijkowy
    ?>
  3. <?php
    /* to jest komentarz
    wielolinijkowy */
    ?>

Moja ciekawostka dotyczy wersji 1 i 2. Teraz pytanie, dość proste: co zostanie wyświetlone w wyniku parsowania pliku PHP o treści:

<?php
echo 'aaa?>';
echo 'bbb';
?>

Dość oczywistym jest wynik:

aaa?>bbb

No to teraz pytanie drugie: co będzie wynikiem parsowania poniższego skryptu?

<?php
#echo 'aaa?>';
echo 'bbb';
?>

Dla mnie, i dla conajmniej kilku innych osób które od dość długiego czasu zajmują się PHP, oczywistym jest wynik:

bbb

Ale dla developerów PHP logicznym jest inny rezultat:

'; echo 'bbb'; ?> 

Cytat z manuala:

The “one-line” comment styles only comment to the end of the line or the current block of PHP code, whichever comes first. This means that HTML code after // … ?> or # … ?> WILL be printed: ?> breaks out of PHP mode and returns to HTML mode, and // or # cannot influence that. If the asp_tags configuration directive is enabled, it behaves the same with // %> and # %>.

Źródło: manual PHP


Strasznie mnie wkurza nabyta niedawno maniera Akismet-a na stronicowanie listy spamerskich komentarzy dodanych na WordPressie. Nie wiem też czy poprawili to co było na początku po tej zmianie, czyli jeśli w międzyczasie, gdy byłeś na stronie 4 spamów, sprawdzając czy coś się nie nawinęło, a w międzyczasie doszły nowe komentarze, to wyświetlane były na stronie 1, która była już przez Ciebie sprawdzana… I tak oto można było wywalić w kosmos kilka, kilkanaście pozytywnych komentarzy.

Poprawienie tego “błędu” (mówię ogólnie o stronicowaniu spamów) jest dość proste - w obecnej, 2.1.2, wersji Wordpressa, w pliku /wp-content/plugins/akismet/akismet.php wywalamy linie 365 do 372 włącznie, wstawiając zamiast nich:

$comments = $wpdb->get_results("SELECT * FROM $wpdb->comments WHERE comment_approved = 'spam' ORDER BY comment_date DESC");

Wywalamy także linie: 379 do 410 włącznie, i 449 do 479 włącznie. Od tej chwili Akismet nie będzie już stronicował wyników :)

Można też ściągnąć gotowego patcha (akismet.wordpress.212.patch) do Wordpress-a, do głównego katalogu Wordpressa w Twojej instalacji i go zaaplikować:

patch -p0 < akismet.wordpress.212.patch

co powinno być nieco prostsze ;) o ile masz dostęp do jakiegoś shella :) Jako bonus, patch spowoduje także numerowanie wszystkich spamów na liście :)